简介
Environment
是OWL框架的概念,他是组件之间共享的一个对象。OWL框架本身并不使用Environment
,Environment
的用途就是为组件之间的通信提供一个简单的通道,OWL APP可以根据需要在Environment
中定义需要的属性,例如:
- 应用的配置信息
- session
- 通用的服务(
rpc
) - 工具函数(
_t
) - ...
Environment
的传递
组件在构造时会使用父组件的childEnv
属性来创建自己的env,如果childEnv
为空,则会使用全局env。下面为OWL源码
const env = (parent && parent.childEnv) || app.env;
this.childEnv = env;
...
// 使用env创建新组件
this.component = new C(props, env, this);
const ctx = Object.assign(Object.create(this.component), { this: this.component });
Environment
的类型
Environment
根据作用范围的不同分为全局env与SubEnv
全局env
当OWL APP创建时,APP的构造函数接收一个env参数,构造函数内部使用env作为原型,创建一个新的冻结(freeze)对象,该对象作为全局对象在应用的组件之间进行共享。(源码)
- Odoo的Webclient是一个OWL APP,Webclient在启动时也会接收一个env参数,在开发中常用的
services
就保存在这个env中(源码)
SubEnv
有些情况下,属性只需要在组件及其子组件之间进行共享,并不需要对应用的所有组件进行暴露,这种情况就需要用到SubEnv对组件的env进行扩展。如何使用SubEnv,见下文。
修改Environment
对象
OWL使用Object.freeze
(freeze方法文档)方法对env的属性进行了冻结,因此不可以直接修改env。要对env进行修改,需要使用OWL提供的钩子。
- 注意:修改后的env仍然是一个冻结对象。(源码)
useChildSubEnv
setup() {
useChildSubEnv({ someKey: "value" })
}
只对子组件的env扩展someKey
属性,当前组件的env不会扩展someKey
属性
useSubEnv
setup() {
useSubEnv({ someKey: "value" })
}
使用该方法后当前组件及其子组件的env都会扩展someKey
属性。该方法内部调用了useChildSubEnv
Environment
在开发中的应用场景
env.searchModel
视图可以调用this.env.searchModel
export class WithSearch extends Component {
setup() {
...
const SearchModelClass = this.props.SearchModel || SearchModel;
this.searchModel = new SearchModelClass(this.env, {
user: useService("user"),
orm: useService("orm"),
view: useService("view"),
});
// 视图是WithSearch的子组件,见下面的xml
useSubEnv({ searchModel: this.searchModel });
...
}
}
<t t-name="web.View" owl="1">
<WithSearch t-props="withSearchProps" t-slot-scope="search">
<t t-component="Controller"
t-on-click="handleActionLinks"
t-props="componentProps"
context="search.context"
domain="search.domain"
groupBy="search.groupBy"
orderBy="search.orderBy"
comparison="search.comparison"
display="search.display"/>
</WithSearch>
</t>
env.inDialog
当视图在Dialog
中打开时,布局、视图、字段、以及其他子组件的env
都可以通过env.inDialog
来判断是否在弹出中。
export class Dialog extends Component {
setup() {
...
useChildSubEnv({ inDialog: true, dialogId: this.id, closeDialog: this.data.close });
...
}
...
}
Layout组件
<t t-if="env.inDialog" t-portal="'#' + env.dialogId + ' .modal-footer'">
BomOverviewComponent
BomOverviewComponent
组件用于展示BOM信息,用户可以点击展开按钮,查看子BOM信息。该组件使用useSubEnv
为自己以及后代组件扩展了一个EventBus
对象,该对象在BomOverviewComponent
及后代组件中进行共享,可以方便组件间的通信。
扩展EventBus
export class BomOverviewComponent extends Component {
setup() {
...
useSubEnv({
overviewBus: new EventBus(),
});
...
}
...
}
// 子组件
BomOverviewComponent.components = {
BomOverviewControlPanel,
BomOverviewTable,
};
使用EventBus
export class BomOverviewControlPanel extends Component {
setup() {
...
// 展开按钮
// <button t-on-click="clickUnfold" ...>Unfold</button>
clickUnfold() {
// 触发 unfold-all 事件
this.env.overviewBus.trigger("unfold-all");
}
...
}
// 注意:BomOverviewComponentsBlock是BomOverviewTable的子组件
// 通过SubEnv扩展的EventBus可以在没有直接关系的组件之间进行通信
// 如果没有这种机制,想要通过控制面板的按钮来对单行BOM信息进行操作,将会很复杂,组件之间还会产生耦合
// 比如使用通过props向子组件传递回调函数,只是简单的父子组件还好,如果是多级传递那代码会非常混乱
export class BomOverviewComponentsBlock extends Component {
setup() {
...
// 如果有子组件,则监听 unfold-all 事件
if (this.hasComponents) {
useBus(this.env.overviewBus, "unfold-all", () => this._unfoldAll());
}
...
}
...
// 展开子组件
_unfoldAll() {
const allChildIds = this.childIds;
this.state.unfoldAll = true;
allChildIds.forEach(id => this.state[id] = false);
this.props.changeFolded({ ids: allChildIds, isFolded: false });
}
...
}