Odoo Web:Environment解析

114 阅读3分钟

简介

Environment是OWL框架的概念,他是组件之间共享的一个对象。OWL框架本身并不使用EnvironmentEnvironment的用途就是为组件之间的通信提供一个简单的通道,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.freezefreeze方法文档)方法对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 });
    }
    ...
}