修补代码

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

然而,在某些情况下这还不够。在这些情况下,我们可能需要就地修改对象或类。为了实现这一点,Odoo 提供了实用函数 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`` function. To do so, we can simply use the native ``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 类

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 以一种特殊的本机方式处理构造函数,这使得它不可能被修补。唯一的解决方法是调用原始构造函数中的方法并修补该方法:

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 方法,这样它们也可以轻松地进行修补(请参阅关于 best practices 的部分)。

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

删除补丁

patch 函数返回其对应项。当我们在测试开始时修补某些内容并在测试结束时取消修补时,这对于测试目的非常有用。

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

将相同的补丁应用到多个对象

可能会发生这样的情况:您想要将相同的补丁应用于多个对象,但由于 super 关键字的工作方式,extension 只能用于修补一次,并且无法复制/克隆 (check the documentation of the keyword)。返回用于修补的对象的函数可用于使其唯一。

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