🦉 Компонент OWL 🦉

Содержание

Введение

Компоненты OWL это строительные блоки пользовательского интерфейса. Они спроектированы обладать следующими свойствами:

  1. declarative: пользовательский интерфейс следует описывать с точки зрения состояния приложения, а не как последовательность императивных шагов

  2. composable: каждый компонент может быть легко создан в родительском компоненте с помощью простого тега или директивы в его шаблоне.

  3. asynchronous rendering: фреймворк будет ждать готовности каждого подкомпонента перед применением рендеринга. Он использует нативные промисы под капотом.

  4. uses QWeb as a template system: шаблоны описаны в XML и соответствуют спецификации QWeb. Это требование для Odoo.

OWL компоненты определяются как дочерние классы Component. Рендеринг осуществляется исключительно QWeb шаблоном (который требуется загрузисть предварительно в QWeb) Рендеринг компонента генерирует виртуальное представление компонента, которое затем прикрепляется к DOM, чтобы применить изменения эффективным способом.

Пример

Давайте посмотрим на простой компонент:

const { useState } = owl.hooks;

class ClickCounter extends owl.Component {
  state = useState({ value: 0 });

  increment() {
    this.state.value++;
  }
}
<button t-name="ClickCounter" t-on-click="increment">
  Нажми меня! [<t t-esc="state.value"/>]
</button>

Обратите внимание что код написан в стиле ESNext, это означает, что он будет работать только на последних версиях браузеров без шага транспиляции

Этот пример показывает как компонент должен определяться: это просто класс наследник класса Component. Если нет ключа template, то в этом случае Owl будет использовать имя компонента как имя шаблона. Здесь определен объект состояния путем использования хука useState. Использование объекта состояния не обязательно, но, безусловно, приветствуется. Результатом вызова useState является observed и лбое его изменение вызовет повторный рендеринг.

Описание элементов

Компонент Owl это маленький класс который представляет компонент или какой либо UI элемент. Он существует в контексте окружения(env), доступ к которому распространяется от родителя к своим детям. Окружение должно иметь экземпляр QWeb, который будет исползоваться для рендеринга шаблона компонента.

Имейте в виду, что имя компонента может быть важным: если компонент не определяет ключ template, тогда Owl будет искать в QWeb шаблон с именем компонента (или одним из его предков).

Реактивная система

Owl компоненты - это нормальные классы javascript. Поэтому изменение внутреннего состояния компонента ничего не делает:

class Counter extends Component {
  static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
  state = { value: 0 };

  increment() {
    this.state.value++;
  }
}

Кликая на компонент Counter, определенный выше, вызовет increment метод, но он не отрендерит компонент заново. Для того чтобы это исправить можно добавить явный вызов метода render внутри increment:

    increment() {
        this.state.value++;
        this.render();
    }

Однако, это может быть самым простым способом для данного случая, но он быстро станет громоздким, вместе с ростом сложности самого компонента когда его внутреннее состояние будет меняться уже более чем одним методом

Правильный способ - это использовать реакционную систему: используя хук useState (см. секцию Хуки для изучения подробностей), он может заставить Owl реагировать на изменение состояния. Хук useState генерирует прокси версию объекта (это создается с помощью обозревателя), который позволяет компоненту реагировать на любые изменения. Поэтому пример Counter может быть улучшен примерно вот так:

const { useState } = owl.hooks;

class Counter extends Component {
  static template = xml`<div t-on-click="increment"><t t-esc="state.value"/></div>`;
  state = useState({ value: 0 });

  increment() {
    this.state.value++;
  }
}

Очевидно, мы можем вызывать хук useState более одного раза:

const { useState } = owl.hooks;

class Counter extends Component {
  static template = xml`
      <div>
        <span t-on-click="increment(counter1)"><t t-esc="counter1.value"/></span>
        <span t-on-click="increment(counter2)"><t t-esc="counter2.value"/></span>
      </div>`;
  counter1 = useState({ value: 0 });
  counter2 = useState({ value: 0 });

  increment(counter) {
    counter.value++;
  }
}

Обратите внимание что хуки подчиняются одному важному правилу: они требуют, чтобы их вызывали в конструкторе.

Свойства (Properties)

  • el (HTMLElement | null): ссылка на корневой узел DOM элемента. Является null, когда компонент не смонтирован.

  • env (Object): компонент окружения, который содержит экземпляр QWeb.

  • props (Object): это объект который содержит свойства предоставленные родителем своим дочерним компонентам. Например следующая ситуация, родительский компонент предоставляет user и color значения для ChildComponent

      <div>
        <ChildComponent user="state.user" color="color">
      </div>
    

    Обратите внимание что props принадлежат родителю, не компоненту. Поэтому они никогда не должны изменяться компонентов (в противном случае вы рискуете получить непредсказуемые последствия поскольку родитель может не знать об этих изменениях)

    props могут динамически изменяться родителем. В этом случае, компонент будет проходить через методы жизненного цикла: willUpdateProps, willPatch и patched.

Статические свойства (Static Properties)

  • template (string, optional): если определен то это будет имя шаблона QWeb, который будет редндерить компонент. Обратите внимание, что есть вспомогательный xml, облегчающий определение встроенного шаблона.
  • components (Object, optional): если определен то это будет объект, который содержит классы любых дочерних компонентов, необходимых шаблону. Это основной способ, используемый Owl для создания дочерних компонентов.

    class ParentComponent extends owl.Component {
      static components = { SubComponent };
    }
    
  • props (Object, optional): если определен то это будет объект который описывает тип и форму пропсов предоставленных компоненту. Если Owl находится в режиме dev, то можно использовать для валидации пропсов каждый раз когда компонент создается/обновляется. Смотрите Props Validation для получения более детальной информации

    class Counter extends owl.Component {
      static props = {
        initialValue: Number,
        optional: true,
      };
    }
    
  • defaultProps (Object, optional): если определен то это будет объект который значения по умолчнию для пропсов. Всякий раз, когда props передаются объекту, они будут изменены, чтобы добавить значение по умолчанию (если оно отсутствует). Обратите внимание, что исходный объект не изменяется, вместо этого создается новый объект.

    class Counter extends owl.Component {
      static defaultProps = {
        initialValue: 0,
      };
    }
    
  • style (string, optional): должно быть возвращаемое значение css тега, который используется для внедрения таблицы стилей всякий раз, когда компонент виден на экране.

В классе Component определено еще одно статическое свойство: current. Это свойство устанавливается для определяемого в данный момент компонента (в конструкторе). Таким образом хуки могут получить ссылку на целевой компонент.

Методы

Здесь мы объясним все публичные методы класса Component.

  • mount(target, options) (async): это основной способ добавления компонента в DOM: корневой компонент монтируется в целевой HTMLElement (или фрагмент документа). Очевидно, что это происходит асинхронно, так как каждый дочерний элемент также должен быть создан. Большинству приложений потребуется вызвать mount ровно один раз для корневого компонента.

    Аргумент options является необзательным объектом с ключем position. Ключ position может иметь три возможных значения: first-child, last-child, self.

    • first-child: с этим параметров компонент будет добавлен внутрь целевого элемента на первое место,
    • last-child (значение по умолчанию): с этим параметром компонент будет добавляться на поседнее место внутрь целевого элемента,
    • self: цель будет использоваться как корневой элемент для компонента. Это означает, что целью должен быть
      HTMLElement (а не фрагмент документа). В этой ситуации возможно, что компонент не может быть отмонтирован. Например, если его целью является document.body.

    Обратите внимание что если компонент монтируется, отмонтируется и перемонтируется, то он будет автоманически перерендерен, чтобы убедиться что изменения в состоянии (что-то в окружении), или в хранилище, или...) буду учтены.

    Если компонент примонтирован внутрь элемента или фрагмена которые не в DOM, тогда он будет перерендерен целиком, но не активен: хуки mounted не вызовутся. Это иногда полезно, если мы хотим загрузить приложение в память. В этом случае нам нужно примонтировать корневой компонент заново в элементе, который находится в DOM:

    const app = new App();
    await app.mount(document.createDocumentFragment());
    // app is rendered in memory, but not active
    await app.mount(document.body);
    // app is now visible
    

    Обратите внимание что нормальный способ монтирования приложения это исползование метода mount в классе компонента, а не через создание экземпляра вручную. Смотрите документа по монтированию приложений.

  • unmount(): этот метод может быть использован в случаях когда компонент, необходимо отсоеденить/удалить из DOM. Большинство приожений не должны вызивать unmount, это подходит больше для нижележащих системных компонентов.

  • render() (async): вызов этого метода сразу вызовет повторный ререндеринг.

    Обратите внимание что ручное использование этого метода должно быть редким случаем т.к. фреймворк Owl в большинстве случаев сам отвечает за это в нужный момент.

    Так же обратите внимание что данный метод является асинхронным, поэтому не может отследить обновление DOM в том же кадре стека.

  • shouldUpdate(nextProps): этот метод вызывается каждый раз когда обновляются пропсы компонента. Он возвращает булево значение, которое говорит о том может ли компонент игнорировать эти изменения или нет. Если он возвращает false, тогда метод willUpdateProps не будет вызван и ререндеринга не будет. Его реализация по умолчанию всегда возвращает true. Обратиче внимание что это оптимизация аналогичная shouldComponentUpdate в React. Большую часть времени он не должен использоваться,но если он может быть полезным в случаях кода мы управляем большим количество компонентов. Поскольку это оптимизация, то Owl имеет возможность игнорировать резулльтат shouldUpdate в некоторых случаях (например если компонент перемонтируется, или если мы хотим принудительно перерендерит весь UI). Как бы там ни было, если shouldUpdate возвращает true, тогда Owl гарантирует нам, что компонент будет перерендерен в какой-то момент в будущем (за исключением случаев, когда компонент будет уничтожен или произойдет сбой какой-либо части пользовательского интерфейса).

  • destroy(). Как следует из названия, этот метод удалит компонент и выполнит всю необходимую работу, например, размонтирует компонент, его дочерние элементы, удалив отношения родитель/потомок. Этот метод почти никогда не следует вызывать напрямую (за исключением, возможно, корневого компонента) вместо этого он должен выполняться фреймворком.

Очевидно, что эти методы зарезервированы для Owl и не должны использоваться пользователями Owl, если только они не захотят переопределить их. Кроме того, Owl резервирует все имена методов, начинающиеся с __, чтобы предотвратить возможные будущие конфликты с пользовательским кодом всякий раз, когда Owl необходимо изменить.

Жизненный цинкл

Крепкая и надежная система компонентов нуждается в полезных хуках/методах, помогающих разработчикам писать компоненты. Вот полное описание жизненного цикла компонента owl:

только после того, как компонент был перерендерен и добавлен в DOM

МетодОписание
setupнастройка
willStartasync, перед первым рендерингом
mountedтолько после того, как компонент был перерендерен и добавлен в DOM
willUpdatePropsasync, перед обновлением пропсов
willPatchнепосредственно перед исправлением DOM
patchedнепосредственно после исправлением DOM
willUnmountнепосредственно перед удалением из DOM
catchErrorперехватиывае ошибки (см перехват ошибок)

Примечания:

  • порядок вызова хуков точно определен: [willX] хуки вызваются сначала у родителя, затем у дочернего объекта, и [Xed] вызываются в обратном порядке, сначала у дочрних объектов, а потом у родителей.
  • никакие методы хуков не должны вызывать самостоятельно. Они должны вызываться фреймворком Owl тогда, когда это требуется.

constructor(parent, props)

constructor — это не совсем хук, это обычный, нормальный конструктор компонента. Поскольку это не хук, вам нужно убедиться, что вызывается super.

Это то место где обычно настраиватеся начальное состояние и шаблон компонента.

  constructor(parent, props) {
    super(parent, props);
    this.state = useState({someValue: true});
    this.template = 'mytemplate';
  }

Обратите внимение ESNext поля классов, метод constructor не требует реализации в большинстве случаев:

class ClickCounter extends owl.Component {
  state = useState({ value: 0 });

  ...
}

Функции хуков могут быть вызваны в коснтруторе.

setup()

setup запускается после того, как компонент был сконструирован. Это метод жизненного цикла, такой же простой как и constructor, за исключением того, что оне не принимает каких либо аргументов.

Это правильный метод для вызова функций хуков. Обритите внимание что одна из основных причин иметь хук setup в жизненном цикле компонента это сделать возможным сделать монкей патч. Это обычная потребность в экосистеме Odoo.

setup() {
  useSetupAutofocus();
}

willStart()

willStart это асинхронный хук, который может быть реализован для выполнения некоторых действий перед первоначальным рендерингом компонента.

Он будет вызван ровно один раз перед начальным рендеринго. Это полезно в некоторых случаях, например, загрузить внешние ассеты (такие как JS библиотеки) перед тем как как компонент их отрендерит. В другом случае можно загрузить данные с сервера

  async willStart() {
    await owl.utils.loadJS("my-awesome-lib.js");
  }

В этом месте, компонент еще не отрендерен. Обратите внимание что медленный метод willStart будет замедлять рендеринг пользовательского интерфейса. Поэтому следует позаботиться о том, чтобы этот метод отрабатывал как можно быстрее.

mounted()

mounted метод вызывается каждый раз когда компонент прикрепляется к DOM, после первоначального рендеринга и возможно позже, если компонент был отмпонтирован и перемонтирован. В этой точке, компонент считается active. Это хорошее место для добавления нескольких слушателей или для взаимодействия с DOM, если, например, компоненту необходимо выполнить какое-то действие.

Это противоположность willUnmount. Если компонент был смонтирован, он всегда будет размонтирован в какой-то момент в будущем.

Метод mounted будет вызываться рекурсивно для каждого дочернего элемента. Сначала для родителя, затем для всех детей.

Так же позволяется (но не поощраяется) модифицировать состояние в хуке mounted.

It is allowed (but not encouraged) to modify the state in the mounted hook. Это вызовет повторный рендеринг, который не будет заметен для пользователя, но немного замедлит работу компонента.

willUpdateProps(nextProps)

willUpdateProps это асинхронный хук, вызвается перед установкой новых пропсов. Это полезно если компонент нуждается в выполнении асинхронной задачи, зависящей от пропсов (анпример, предполагая, что пропсы являются некоторым id записи, при извлечении данных записи)

  willUpdateProps(nextProps) {
    return this.loadData({id: nextProps.id});
  }

Этот хук не вызывается в момент первого рендеринга (но willStart вызывается и выполняет простую работу).

willPatch()

willPatch хук вызывается непосредственно перед началом процесса исправления DOM. Он не вызывается при начальном рендеринге. Это полезно для чтения информации из DOM. Например, текущее положение полосы прокрутки.

Обратите внимание что изменение состояние здесь не допускается. Этот метод вызывается только переде конкретным изменением DOM, и предназначен только для сохранения локального состояния DOM. Также он не будет вызываться, если компонента нет в DOM.

patched(snapshot)

Этот хук вызывается всякий раз, когда компонент действительно обновляет свой DOM (скорее всего, через изменение своего состояния/пропсов или окружения).

Этот метод не вызывается при начальном рендеринге. Он может быть полезен для взаимодействия с DOM (например, через внешнюю библиотеку) всякий раз, когда компонент был исправлен. Обратите внимание, что этот хук не будет вызываться, если компонент не находится в DOM.

Обновление состояния компонента в этом хуке возможно, но не рекомендуется. Нужно быть осторожным, потому что обновления здесь создадут дополнительный рендеринг, который, в свою очередь, вызовет другие вызовы метода patched. Таким образом, мы должны быть особенно осторожны чтобы избежать бесконечных циклов.

willUnmount()

willUnmount() это хук который вызывается каждый раз перед тем, как компонент будет отмонтировать из DOM. Это хорошее место для удаления слушателей, например.

  mounted() {
    this.env.bus.on('someevent', this, this.doSomething);
  }
  willUnmount() {
    this.env.bus.off('someevent', this, this.doSomething);
  }

Это метод, противоположный mounted.

catchError(error)

catchError метод полезен, если нам надо перехватить и отреагировать (отрендерить) на ошибки которые возникают в дочерних компонентах. Смотрите на странице обработки ошибок.

Корневой компонент (Root Component)

В большинстве случаев компонент Owl создается автоматически с помощью тега (или директивы t-component) в шаблоне. Однако есть очевидное исключение: корневой компонент приложения Owl должен быть создан вручную:

class App extends owl.Component { ... }

const app = new App();
app.mount(document.body);

Корневой компонент не имеет родителя, и не имеет props (см. примечение ниже). Он будет настроен с окружением (либо env, определенный для его класса, либо пустая среда по умолчанию).

Примечание: корневой компонент воощето может получить объект props в своем коснтрукторе, например так: new App(null, {some: 'object'});. Но это будут не настоящие props, управляемые Owl (поэтому, например они никогда не будут обновляться)

Композиция (Composition)

Пример выше показывает Qweb шаблон с дочерним компонентом. В шаблоне компоненты объявлены с именами тегов соответствующими именам классов. Они должны писаться с большой буквы

<div t-name="ParentComponent">
  <span>some text</span>
  <MyComponent info="13" />
</div>
class ParentComponent extends owl.Component {
    static components = { MyComponent: MyComponent};
    ...
}

В этом примере шаблон ParentComponent создает компонент сразу после тега span. Ключ info будет добавлен к объекут props дочернего компонента. Кадый элемент props это строка, которая представляет собой выражение javascript (QWeb), поэтому она динамическая. Если необходимо передать строковое значение, то вы можете это сделать просто обернув его в одинарные ковычки someString="'somevalue'".

Обратите внимание что контекст для рендеринга для шаблона это и есть сам компонент. Это означате что шаблон может иметь доступ к state (если оно существует), props, env или любому методу определенному в компоненте.

<div t-name="ParentComponent">
    <ChildComponent count="state.val" />
</div>
class ParentComponent {
  static components = { ChildComponent };
  state = useState({ val: 4 });
}

Всякйи когда шаблон рендерится, он будет автоматически создавать дочерний компонент ChildComponent в правильном месте. Это нужно для нахождения ссылко на актуальный класс компонента в специально static ключе components, или класс регистрируется в глобальном реестре QWeb (см. функцию register у QWeb). Сначала он просматривает статический ключ components, а затем обращается к глобальному реестру.

Props: в этом примере дочерний компонент получит объект {count: 4} в своем конструкторе. Это будет присвоено переменной props, к которой можно получить доступ в компоненте (а также в шаблоне). Всякий раз, когда состояние обновляется, дочерний компонент также будет обновляться автоматически. Смотрите раздел props чтобы узнать больше.

CSS и стили: Owl позволяет родителю объявить дополнительные классы CSS или стиль для дочернего компонента: CSS, объявленный в class, style, t-att-class или t-att-style, будет добавлен к элементу корневого компонента.

<div t-name="ParentComponent">
  <MyComponent class="someClass" style="font-weight:bold;" info="13" />
</div>

Предупреждение: есть небольшая оговорка с динамическими атрибутами класса: поскольку Owl должен иметь возможность добавлять/удалять правильные классы, когда это необходимо, он должна знать о налиичи возможных классов. В противном случае он не сможет отличить допустимый класс css, добавленный компонентом или другим пользовательским кодом, от класса, который необходимо удалить. Вот почему мы поддерживаем только явный синтаксис с объектом класса:

<MyComponent t-att-class="{a: state.flagA, b: state.flagB}" />

Привязка к полям ввода форм.

Очень часто требуется иметь возможность прочитать значение из html элемента input (или textarea, или select), чтобы использовать его (примечание: элемент не обязательно должен быть в форме!). Возможный способ сделать это - сделать это вручную:

class Form extends owl.Component {
  state = useState({ text: "" });

  _updateInputValue(event) {
    this.state.text = event.target.value;
  }
}
<div>
  <input t-on-input="_updateInputValue" />
  <span t-esc="state.text" />
</div>

Это работает. Тем не менее, для этого требуется немного вспомогательного кода. Кроме того, вспомогательный код немного отличается, если вам нужно взаимодействовать с checkbox, radio или тегом select.

Чтобы помочь в этой ситуации, Owl имеет встроенную директиву t-model: ее значение должно быть наблюдаемым значением в компоненте (обычно state.someValue). С помощью директивы t-model мы можем написать более короткий код, эквивалентный предыдущему примеру:

class Form extends owl.Component {
  state = { text: "" };
}
<div>
  <input t-model="state.text" />
  <span t-esc="state.text" />
</div>

The t-model directive works with <input>, <input type="checkbox">, <input type="radio">, <textarea> and <select>:

<div>
    <div>Text in an input: <input t-model="state.someVal"/></div>
    <div>Textarea: <textarea t-model="state.otherVal"/></div>
    <div>Boolean value: <input type="checkbox" t-model="state.someFlag"/></div>
    <div>Selection:
        <select t-model="state.color">
            <option value="">Select a color</option>
            <option value="red">Red</option>
            <option value="blue">Blue</option>
        </select>
    </div>
    <div>
        Selection with radio buttons:
        <span>
            <input type="radio" name="color" id="red" value="red" t-model="state.color"/>
            <label for="red">Red</label>
        </span>
        <span>
            <input type="radio" name="color" id="blue" value="blue" t-model="state.color" />
            <label for="blue">Blue</label>
        </span>
    </div>
</div>

Наподобие обработки событий, директива t-model принимает следующие модификаторы:

Like event handling, the t-model directive accepts the following modifiers:

МодификаторОписание
.lazyобновляет значение по событию change( по умолчанию по событию input)
.numberпытается пробразовать значение в число (использует parseFloat)
.trimсрезает пробелы по края значения

Например:

<input t-model.lazy="state.someVal" />

Эти модификаторы могут комбинироваться. Н

These modifiers can be combined. В этом случае, t-model.lazy.number будет только обновлять число при каждом изменении.

Примечание: онлайн площадка имеет пример того, как это работает.

Ссылки (References)

Хук useRef полезен когда нам нужен способ взаимодействия с какой либо внутреннней частью компонента, отрендеренного Owl. Он может работать как элемент с элементом DOM так и с компонентом, при помощи директивы t-ref. Больше подробностей в разделе Хуки.

Короткий пример того как мы может установить фокус на заданный input:

<div>
    <input t-ref="input"/>
    <button t-on-click="focusInput">Click</button>
</div>
import { useRef } from "owl/hooks";

class SomeComponent extends Component {
  inputRef = useRef("input");

  focusInput() {
    this.inputRef.el.focus();
  }
}

Хук useRef может также использоваться для получения ссылок на экземпляр дочернего компонента отрендреренного Owl. В этом случае нам нужно получить к нему доступ с помощью свойства comp вместо el:

The useRef hook can also be used to get a reference to an instance of a sub component rendered by Owl. In that case, we need to access it with the comp property instead of el:

<div>
    <SubComponent t-ref="sub"/>
    <button t-on-click="doSomething">Click</button>
</div>
import { useRef } from "owl/hooks";

class SomeComponent extends Component {
  static components = { SubComponent };
  subRef = useRef("sub");

  doSomething() {
    this.subRef.comp.doSomeThingElse();
  }
}

Обратите внимание, что в этих двух примерах используется суффикс ref для названия ссылки. Это не обязательно, но это полезное соглашение, поэтому мы не забываем обращаться к нему с суффиксом el или comp.

Динамические дочерние компоненты

Это не обычно, но иногда нам нужно имя динамического компонента. В этом случае директива t-component также может использоваться для принятия динамических значений с интерполяцией строк (например, директива [t-attf-] qweb_templating_language.md#dynamic-attributes):

<div t-name="ParentComponent">
    <t t-component="ChildComponent{{id}}" />
</div>
class ParentComponent {
  static components = { ChildComponent1, ChildComponent2 };
  state = { id: 1 };
}

Существует еще более динамичный способ использования t-component: его значение может быть выражением, исполняющим сам класс компонента. В этом случае это класс, который будет использоваться для создания компонента:

class A extends Component<any, any, any> {
  static template = xml`<span>child a</span>`;
}
class B extends Component<any, any, any> {
  static template = xml`<span>child b</span>`;
}
class App extends Component<any, any, any> {
  static template = xml`<t t-component="myComponent" t-key="state.child"/>`;

  state = { child: "a" };

  get myComponent() {
    return this.state.child === "a" ? A : B;
  }
}

В этом примере компонент App выбирает динамически кокретный класс дочернего компонента

Обратите внимание что директива t-component может быть использована только в <t> элементах

Функциональные компоненты

Owl имеет не совсем функциональные компоненты. Однка, это очень близкая альтернатива: вызов дочерних шаблонов.

Функциональный компонент без сохранения состояния в react обычно представляет собой какую-то функцию, которая сопоставляет пропсы с виртуальным DOM (часто с помощью jsx). Почти как шаблон, отредндеренный с помощью props. В Owl это можно сделать, просто определив шаблон, который будет обращаться к объекту props:

const Welcome = xml`<h1>Hello, <t t-esc="props.name"/></h1>`;

class MyComponent extends Component {
  static template = xml`
        <div>
            <t t-call=${Welcome}/>
            <div>something</div>
        </div>
    `;
}

Это работает так, что дочерние шаблоны встроены и имеют доступ к внешнему контексту. Поэтому они могут получить доступ к props и любой другой части вызывающего компонента.

SVG компоненты

Компоненты Owl могут быть использован для создания динамической SVG графики:

class Node extends Component {
  static template = xml`
        <g>
            <circle t-att-cx="props.x" t-att-cy="props.y" r="4" fill="black"/>
            <text t-att-x="props.x - 5" t-att-y="props.y + 18"><t t-esc="props.node.label"/></text>
            <t t-set="childx" t-value="props.x + 100"/>
            <t t-set="height" t-value="props.height/(props.node.children || []).length"/>
            <t t-foreach="props.node.children || []" t-as="child">
                <t t-set="childy" t-value="props.y + child_index*height"/>
                <line t-att-x1="props.x" t-att-y1="props.y" t-att-x2="childx" t-att-y2="childy" stroke="black" />
                <Node x="childx" y="childy" node="child" height="height"/>
            </t>
        </g>
    `;
  static components = { Node };
}

class RootNode extends Component {
  static template = xml`
        <svg height="180">
            <Node node="graph" x="10" y="20" height="180"/>
        </svg>
    `;
  static components = { Node };
  graph = {
    label: "a",
    children: [
      { label: "b" },
      { label: "c", children: [{ label: "d" }, { label: "e" }] },
      { label: "f", children: [{ label: "g" }] },
    ],
  };
}

Компонент RootNode будет отображать живое SVG-представление графика, описанного свойством graph. Обратите внимание, что здесь присутствует рекурсивная структура: компонент Node использует себя как подкомпонент.

Обратите внимание, что поскольку SVG необходимо обрабатывать особым образом (его пространство имен должно быть правильно установлено), существует небольшое ограничение для компонентов Owl: если предполагается, что компонент owl является частью графа svg, то его корневой узел должен быть тегом g, чтобы Owl мог п равильно установить пространство имен.