补丁代码

有时,我们需要自定义UI的工作方式。许多常见需求都可以通过一些支持的API来实现。例如,所有的注册表都是很好的扩展点:字段注册表允许添加/删除专门的字段组件,或者主组件注册表允许添加应始终显示的组件。

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

描述

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

patch(objToPatch, extension)
参数
  • objToPatch (object()) – the object that should be patched

  • extension (object()) – an object mapping each key to an extension

返回

a function to remove the patch

The patch function modifies in place the objToPatch object (or class) and applies all key/value described in the extension object. An unpatch function is returned, so it can be used to remove the patch later if necessary.

Most patch operations provide access to the parent value by using the native super keyword (see below in the examples).

补丁一个简单对象

这是一个简单的示例,展示了如何对一个对象进行打补丁:

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

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

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

When patching functions, we usually want to be able to access the parent function. To do so, we can simply use the native super keyword:

patch(object, {
  fn() {
    super.fn(...arguments);
    // do other things
  },
});

警告

super can only be used in a method not a function. This means that the following constructs are invalid for 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

补丁组件

Components are defined by javascript classes, so all the information above still holds. For these reasons, Owl components should use the setup method, so they can easily be patched as well (see the section on best practices).

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

移除一个补丁

The patch function returns its counterpart. This is mostly useful for testing purposes, when we patch something at the beginning of a test, and unpatch it at the end.

const unpatch = patch(object, { ... });
// test stuff here
unpatch();

Applying the same patch to multiple objects

It could happen that one wants to apply the same patch to multiple objects but because of the way the super keyword works, the extension can only be used for patching once and cannot be copied/cloned (check the documentation of the keyword). A function returning the object used to patch can be used to make it unique.

const obj1 = {
  method() {
    doSomething();
  },
};

const obj2 = {
  method() {
    doThings();
  },
};

function createExtensionObj() {
  return {
    method() {
      super.method();
      doCommonThings();
    },
  };
}

patch(obj1, createExtensionObj());
patch(obj2, createExtensionObj());

警告

If an extension is based on another then the two extensions should be applied separately. Do not copy/clone an 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