代码补丁

有时,我们需要自定义 UI 的工作方式。 许多常见需求已由受支持的 API 覆盖。例如,所有注册表都是很好的扩展点:字段注册表允许添加/移除专用字段组件,主组件注册表允许添加应当始终显示的组件。

然而,有些情况下这还不够。在这些情况下,我们可能需要就地修改某个对象或类。为实现这一点,Odoo 提供了工具函数 patch。它主要用于覆盖/更新自己无法控制的某个其他组件/代码片段的行为。

说明

patch 函数位于 @web/core/utils/patch:

patch(obj, patchName, patchValue, options)
参数
  • obj (Object()) – object that should be patched

  • patchName (string()) – unique string describing the patch

  • patchValue (Object()) – an object mapping each key to a patchValue

  • options (Object()) – option object (see below)

patch 函数会就地修改 obj 对象(或类),并应用 patchValue 对象中描述的所有 key/value。该操作以 patchName 名称注册,因此在必要时可以在稍后取消 patch。

大多数 patch 操作通过 _super 属性提供对父级值的访问(见下方示例)。为此,patch 方法会将每一对 key/value 包装在 getter 中,从而动态绑定 _super。

唯一的选项是 pure (boolean)。如果设为 true,patch 操作不会绑定 _super 属性。

为简单对象打补丁

下面是一个如何为对象打补丁的简单示例:

import { patch } from "@web/core/utils/patch";

const object = {
  field: "a field",
  fn() {
    // do something
  },
};

patch(object, "patch name", {
  fn() {
    // do things
  },
});

在对函数进行 patch 时,我们通常希望访问 parent 函数。由于我们处理的是 patch 对象而不是 ES6 类,因此不能使用原生的 super 关键字。所以 Odoo 提供了一种特殊方法来模拟这种行为:this._super:

patch(object, "_super patch", {
  fn() {
    this._super(...arguments);
    // do other things
  },
});

Warning

每次调用被 patch 的函数后,this._super 都会被重新赋值。这意味着如果您在 patch 中使用异步函数,那么在 await 之后就不能调用 this._super,因为它可能不是您所期望的那个函数。正确的做法是保留对初始 _super 方法的引用:

patch(object, "async _super patch", {
  async myAsyncFn() {
    const _super = this._super.bind(this);
    await Promise.resolve();
    await _super(...arguments);
    // await this._super(...arguments); // this._super is undefined.
  },
});

也支持 getter 和 setter:

patch(object, "getter/setter patch", {
  get number() {
    return this._super() / 2;
  },
  set number(value) {
    this._super(value * 2);
  },
});

为 javascript 类打补丁

patch 函数的设计目标是适用于任何对象:对象或 ES6 类。

不过,由于 javascript 类基于原型继承,当想为类的某个标准方法打补丁时,实际上需要为 prototype 打补丁:

class MyClass {
  static myStaticFn() {...}
  myPrototypeFn() {...}
}

// this will patch static properties!!!
patch(MyClass, "static patch", {
  myStaticFn() {...},
});

// this is probably the usual case: patching a class method
patch(MyClass.prototype, "prototype patch", {
  myPrototypeFn() {...},
});

此外,Javascript 以特殊的原生方式处理 constructor,使其无法被打补丁。唯一的变通办法是在原始 constructor 中调用一个方法,然后改为为该方法打补丁:

class MyClass {
  constructor() {
    this.setup();
  }
  setup() {
    this.number = 1;
  }
}

patch(MyClass.prototype, "constructor", {
  setup() {
    this._super(...arguments);
    this.doubleNumber = this.number * 2;
  },
});

Warning

无法直接为类的 constructor 打补丁!

为组件打补丁

组件由 javascript 类定义,因此上述所有信息依然适用。基于这些原因,Owl 组件应使用 setup 方法,这样也便于对它们进行 patch (参见 最佳实践。

patch(MyComponent.prototype, "my patch", {
  setup() {
    useMyHook();
  },
});

移除补丁

patch 函数有一个对应的 unpatch 函数,同样位于 @web/core/utils/patch 中。

unpatch(obj, patchName)
参数
  • obj (Object()) – object that should be unpatched

  • patchName (string()) – string describing the patch that should be removed

从对象 obj 上移除现有的 patch。这主要用于测试场景:在测试开始时对某对象进行 patch,在结束时取消 patch。

patch(object, "patch name", { ... });
// test stuff here
unpatch(object, "patch name");