代码补丁¶
有时,我们需要自定义 UI 的工作方式。 许多常见需求已由受支持的 API 覆盖。例如,所有注册表都是很好的扩展点:字段注册表允许添加/移除专用字段组件,主组件注册表允许添加应当始终显示的组件。
然而,有些情况下这还不够。在这些情况下,我们可能需要就地修改某个对象或类。为实现这一点,Odoo 提供了工具函数 patch。它主要用于覆盖/更新自己无法控制的某个其他组件/代码片段的行为。
说明¶
patch 函数位于 @web/core/utils/patch:
- patch(objToPatch, extension)¶
- 参数
objToPatch (
object()) – 需要打补丁的对象extension (
object()) – 将每个键映射到一项扩展的对象
- 返回
用于移除补丁的函数
patch函数会就地修改objToPatch对象(或类),并应用extension对象中描述的所有键/值。 它会返回一个取消补丁的函数,以便日后在需要时移除补丁。大多数补丁操作都允许通过原生的
super关键字访问父值(参见下文的示例)。
为简单对象打补丁¶
下面是一个如何为对象打补丁的简单示例:
import { patch } from "@web/core/utils/patch";
const object = {
field: "a field",
fn() {
// do something
},
};
patch(object, {
fn() {
// do things
},
});
在为函数打补丁时,我们通常希望能够访问 parent 函数。 为此,我们只需使用原生 super 关键字即可:
patch(object, {
fn() {
super.fn(...arguments);
// do other things
},
});
Warning
super 只能在方法中使用,而不能在函数中使用。这意味着以下结构在 javascript 中是无效的。
const obj = {
a: function () {
// Throws: "Uncaught SyntaxError: 'super' keyword unexpected here"
super.a();
},
b: () => {
// Throws: "Uncaught SyntaxError: 'super' keyword unexpected here"
super.b();
},
};
也支持 getter 和 setter:
patch(object, {
get number() {
return super.number / 2;
},
set number(value) {
super.number = value;
},
});
为 javascript 类打补丁¶
patch 函数的设计目标是适用于任何对象:对象或 ES6 类。
不过,由于 javascript 类基于原型继承,当想为类的某个标准方法打补丁时,实际上需要为 prototype 打补丁:
class MyClass {
static myStaticFn() {...}
myPrototypeFn() {...}
}
// this will patch static properties!!!
patch(MyClass, {
myStaticFn() {...},
});
// this is probably the usual case: patching a class method
patch(MyClass.prototype, {
myPrototypeFn() {...},
});
此外,Javascript 以特殊的原生方式处理 constructor,使其无法被打补丁。唯一的变通办法是在原始 constructor 中调用一个方法,然后改为为该方法打补丁:
class MyClass {
constructor() {
this.setup();
}
setup() {
this.number = 1;
}
}
patch(MyClass.prototype, {
setup() {
super.setup(...arguments);
this.doubleNumber = this.number * 2;
},
});
Warning
无法直接为类的 constructor 打补丁!
为组件打补丁¶
组件由 javascript 类定义,因此上述所有内容仍然适用。 出于这些原因,Owl 组件应使用 setup 方法,这样它们也能方便地被打补丁(参见 最佳实践 一节)。
patch(MyComponent.prototype, {
setup() {
useMyHook();
},
});
移除补丁¶
patch 函数会返回其对应的反向函数。这在测试场景中最为有用:在测试开始时为某物打补丁,结束时再取消补丁。
const unpatch = patch(object, { ... });
// test stuff here
unpatch();
将同一个补丁应用于多个对象¶
可能有人想将同一个补丁应用于多个对象,但由于 super 关键字的工作方式,extension 只能用于一次补丁操作,无法复制/克隆(查看该关键字的文档)。可以使用一个返回补丁所用对象的函数来使其唯一。
const obj1 = {
method() {
doSomething();
},
};
const obj2 = {
method() {
doThings();
},
};
function createExtensionObj() {
return {
method() {
super.method();
doCommonThings();
},
};
}
patch(obj1, createExtensionObj());
patch(obj2, createExtensionObj());
Warning
如果某个 extension 基于另一个扩展,那么这两个扩展应分别应用。不要复制/克隆扩展。
const object = {
method1() {
doSomething();
},
method2() {
doAnotherThing();
},
};
const ext1 = {
method1() {
super.method1();
doThings();
},
};
const invalid_ext2 = {
...ext1, // this will not work: super will not refer to the correct object in methods coming from ext1
method2() {
super.method2();
doOtherThings();
},
};
patch(object, invalid_ext2);
object.method1(); // throws: Uncaught TypeError: (intermediate value).method1 is not a function
const valid_ext2 = {
method2() {
super.method2();
doOtherThings();
},
};
patch(object, ext1); // first patch base extension
patch(object, valid_ext2); // then the new one
object.method1(); // works as expected