🦉 Параллельная модель 🦉

Content

Введение

Owl с самого начала разрабатывался с использованием асинхронных компонентов. Это произошло хуков жизненного цикла willStart и willUpdateProps. С помощью этих методов можно создавать сложные высокопараллельные приложения.

Параллельный режим Owl имеет несколько преимуществ: он позволяет отложить рендеринг до завершения какой-либо асинхронной операции, он позволяет загружать библиотеки в lazy режиме, сохраняя при этом полностью функциональный предыдущий экран. И Это хорошо с точки зрения производительности: Owl использует его только для применения результатов различных рендерингов в одном кадре анимации. Owl может отменить рендеринг, который больше не актуален, перезапустить его, в некоторых случаях использовать его повторно.

Но даже несмотря на то, что использование параллелизма довольно просто (и это поведение по умолчанию), асинхронность сложна, потому что она вводит дополнительное измерение, которое значительно увеличивает сложность приложения. В этом разделе объясняется, как Owl справляется с этой сложностью, как в целом работает конкурентный рендеринг.

Рендеринг компонентов

Слово rendering немного расплывчато, поэтому давайте подробнее объясним процесс, посредством которого компоненты Owl отображаются на экране.

Когда компонент монтируется или обновляется, запускается новая визуализация. Он состоит из двух фаз: virtual rendering и patching.

Виртуальный рендеринг (Virtual rendering)

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

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

Исправление (Patching)

После завершения рендеринга он будет применен к следующему кадру анимации. Это делается синхронно: все дерево компонентов интегрируется в реальный DOM.

Семантика

Мы даем здесь неформальное описание того, как компоненты создаются/обновляются в приложении. Здесь упорядоченные списки описывают действия, которые выполняются последовательно, маркированные списки описывают действия, которые выполняются параллельно.

Scenario 1: initial rendering Представьте что мы хотим отрендерить следующее дерево компонентов:

        A
       / \
      B   C
         / \
        D   E

Вот что происходит всякий раз, когда мы монтируем корневой компонент (с кодом вроде app.mount(document.body)).

  1. willStart вызывается в A

  2. когда он завершен, шаблон A рендерится.

    • компонент B создается
      1. willStart вызывается в B
      2. шаблон B рендерится
    • компонент C создается
      1. willStart вызывается в C
      2. шаблон C рендерится
        • компонент D создается
          1. willStart вызывается в D
          2. шаблон D рендерится
        • компонент E создается
          1. willStart вызывается в E
          2. шаблон E рендерится

каждый компонень 3. каждый компонент подключается к отдельному элементу DOM в следующем порядке: E, D, C, B, A. (поэтому фактическое полное дерево DOM создается за один проход)

  1. компонент A корневой элемент присоединяется к document.body

  2. Метод mounted вызывается рекурсивно у всех компонентов в следующем порядке: E, D, C, B, A.

Scenario 2: ререндеринг компонента. Теперь давайте предположим, что пользователь нажал на какую-то кнопку в C, и это приводит к обновлению состояния, которое должно:

  • обновить D,
  • удалить E,
  • добавить новый компонент F.

И дерево компонентов теперь будет выглядеть вот так:

        A
       / \
      B   C
         / \
        D   F

Вот что сделает Owl:

  1. потому как состояние изменилос, метод render вызывается в C

  2. шаблоно C рендерится снова

    • компонент D обновляется:
      1. хук willUpdateProps вызывается в D (async)
      2. шаблон D ререндерится
    • компонент F is created:
      1. хук willStart вызывается F (async)
      2. шаблон F ререндерится
  3. willPatch хуки вызываются рекурсивно у компонентовC, D (но не F, потому что он все еще не примонтирован)

  4. комоненты F, D присоединяются к DOM в этом порядке

  5. компонент C присоединен к дом, что вызовет рекурсивно:

    1. willUnmount хук у E
    2. уничтожение E,
  6. mounted хук вызовится у F, patched вызовится у D, C

Теги — это очень маленькие помощники, облегчающие написание встроенных шаблонов. В настоящее время доступен только один тег: xml, но позже мы планируем добавить другие теги, например, тег css, который будет использоваться для записи однофайловых компонентов .

Асинхронный рендеринг

Работа с асинхронным кодом всегда усложняет систему. Всякий раз, когда различные части системы активны одновременно, необходимо тщательно продумать все возможные взаимодействия. Это верно и для компонентов Owl.

Есть две разные распространенные проблемы с моделью асинхронного рендеринга Owl:

  • любой компонент может задерживать рендеринг (начальный и последующий) всего приложения
  • для данного компонента есть две независимые ситуации, которые вызовут асинхронный повторный рендеринг: изменение состояния или изменение пропсов. Эти изменения могут быть сделаны в разное время, и Owl не знает, как согласовать полученные визуализации.

Вот несколько советов по работе с асинхронными компонентами:

  1. Минимизируйте использование асинхронных компонентов!

  2. Возможно, переместите асинхронную логику в хранилище, которое затем запускает (в основном) синхронный рендеринг.

  3. Lazy загрузка внешних библиотек — хороший пример использования асинхронного рендеринга. Это нормально, потому что мы делаем допущение, что процесс загрузки займет доли секунды и будет выполняться только один раз (см. owl.utils.loadJS)

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

    <div t-name="ParentComponent">
      <SyncChild />
      <AsyncRoot>
         <AsyncChild/>
      </AsyncRoot>
    </div>