补丁代码¶
有时,我们需要自定义UI的工作方式。许多常见需求都可以通过一些支持的API来实现。例如,所有的注册表都是很好的扩展点:字段注册表允许添加/删除专门的字段组件,或者主组件注册表允许添加应始终显示的组件。
然而,有些情况下这是不够的。在这些情况下,我们可能需要直接修改一个对象或类。为了实现这一点,Odoo提供了实用函数 patch
。它主要用于覆盖/更新某些其他组件/代码片段的行为,而这些组件/代码片段并不受我们控制。
描述¶
patch 函数位于 @web/core/utils/patch
:
- patch(objToPatch, extension)¶
- 参数
objToPatch (
object()
) – 应被修补的对象extension (
object()
) – 一个将每个键映射到扩展的对象
- 返回
移除补丁的函数
patch
函数会就地修改objToPatch
对象(或类),并应用extension
对象中描述的所有键/值。同时会返回一个unpatch
函数,以便在必要时用于移除该补丁。大多数补丁操作通过使用原生的
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
},
});
警告
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 类¶
The patch
function is designed to work with anything: object or ES6 class.
然而,由于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 以一种特殊的本地方式处理构造函数,这使得它无法被修补。唯一的解决方法是调用原始构造函数中的一个方法,然后修补该方法:
class MyClass {
constructor() {
this.setup();
}
setup() {
this.number = 1;
}
}
patch(MyClass.prototype, {
setup() {
super.setup(...arguments);
this.doubleNumber = this.number * 2;
},
});
警告
无法直接修补类的 constructor
!
补丁组件¶
组件由 JavaScript 类定义,因此上述所有信息仍然适用。出于这些原因,Owl 组件应使用 setup
方法,以便它们也能轻松地进行修补(请参阅 最佳实践 部分)。
patch(MyComponent.prototype, {
setup() {
useMyHook();
},
});
移除一个补丁¶
patch
函数会返回其对应的撤销函数。这在测试场景中尤为有用,当我们在测试开始时对某部分进行补丁操作,并在测试结束时撤销该补丁。
const unpatch = patch(object, { ... });
// test stuff here
unpatch();
将相同的补丁应用于多个对象¶
可能会出现这样的情况:人们希望将相同的补丁应用到多个对象上,但由于 super
关键字的工作方式,extension
只能用于一次修补,并且不能被复制或克隆(*请查看关键字的文档 <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/super#description>*_)。可以使用一个返回用于修补的对象的函数来使其唯一。
const obj1 = {
method() {
doSomething();
},
};
const obj2 = {
method() {
doThings();
},
};
function createExtensionObj() {
return {
method() {
super.method();
doCommonThings();
},
};
}
patch(obj1, createExtensionObj());
patch(obj2, createExtensionObj());
警告
如果一个 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