Код виправлення

Іноді нам потрібно налаштувати роботу інтерфейсу користувача. Багато поширених потреб задовольняються деякими підтримуваними API. Наприклад, усі реєстри є гарними точками розширення: реєстр полів дозволяє додавати/видаляти спеціалізовані компоненти полів, або реєстр основних компонентів дозволяє додавати компоненти, які повинні відображатися постійно.

Однак, бувають ситуації, коли цього недостатньо. У таких випадках нам може знадобитися змінити об’єкт або клас на місці. Для досягнення цього Odoo надає корисну функцію patch. Вона здебільшого корисна для перевизначення/оновлення поведінки якогось іншого компонента/фрагмента коду, який не контролюється.

Опис

Функція patch знаходиться в @web/core/utils/patch:

patch(objToPatch, extension)
Аргументи
  • objToPatch (object()) – об’єкт, який слід виправити

  • extension (object()) – об’єкт, що відповідає кожній клавіші розширенню

Повертає

функція для видалення patch

Функція 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();
  },
};

Також підтримуються getters та setters:

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, щоб їх також можна було легко виправити (див. розділ найкращі практики).

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

Removing patch

Функція patch повертає свій аналог. Це здебільшого корисно для цілей тестування, коли ми щось латаємо на початку тесту та відлаштовуємо в кінці.

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

Застосування одного й того ж patch до кількох об’єктів

Може статися так, що хтось хоче застосувати один і той самий патч до кількох об’єктів, але через те, як працює ключове слово super, extension можна використовувати для патча лише один раз і не можна копіювати/клонувати (перевірте документацію до ключового слова). Функція, що повертає об’єкт, який використовується для патча, може бути використана, щоб зробити його унікальним.

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