🦉 Параллельная модель 🦉
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)
).
-
willStart
вызывается вA
-
когда он завершен, шаблон
A
рендерится.- компонент
B
создаетсяwillStart
вызывается вB
- шаблон
B
рендерится
- компонент
C
создаетсяwillStart
вызывается вC
- шаблон
C
рендерится- компонент
D
создаетсяwillStart
вызывается вD
- шаблон
D
рендерится
- компонент
E
создаетсяwillStart
вызывается вE
- шаблон
E
рендерится
- компонент
- компонент
каждый компонень
3. каждый компонент подключается к отдельному элементу DOM в следующем порядке:
E
, D
, C
, B
, A
. (поэтому фактическое полное дерево DOM создается за
один проход)
-
компонент
A
корневой элемент присоединяется кdocument.body
-
Метод
mounted
вызывается рекурсивно у всех компонентов в следующем порядке:E
,D
,C
,B
,A
.
Scenario 2: ререндеринг компонента. Теперь давайте предположим, что пользователь нажал
на какую-то кнопку в C
, и это приводит к обновлению состояния, которое должно:
- обновить
D
, - удалить
E
, - добавить новый компонент
F
.
И дерево компонентов теперь будет выглядеть вот так:
A
/ \
B C
/ \
D F
Вот что сделает Owl:
-
потому как состояние изменилос, метод
render
вызывается вC
-
шаблоно
C
рендерится снова- компонент
D
обновляется:- хук
willUpdateProps
вызывается вD
(async) - шаблон
D
ререндерится
- хук
- компонент
F
is created:- хук
willStart
вызываетсяF
(async) - шаблон
F
ререндерится
- хук
- компонент
-
willPatch
хуки вызываются рекурсивно у компонентовC
,D
(но неF
, потому что он все еще не примонтирован) -
комоненты
F
,D
присоединяются к DOM в этом порядке -
компонент
C
присоединен к дом, что вызовет рекурсивно:willUnmount
хук уE
- уничтожение
E
,
-
mounted
хук вызовится уF
,patched
вызовится уD
,C
Теги — это очень маленькие помощники, облегчающие написание встроенных шаблонов.
В настоящее время доступен только один тег: xml
, но позже мы планируем добавить
другие теги, например, тег css
, который будет использоваться для записи
однофайловых компонентов .
Асинхронный рендеринг
Работа с асинхронным кодом всегда усложняет систему. Всякий раз, когда различные части системы активны одновременно, необходимо тщательно продумать все возможные взаимодействия. Это верно и для компонентов Owl.
Есть две разные распространенные проблемы с моделью асинхронного рендеринга Owl:
- любой компонент может задерживать рендеринг (начальный и последующий) всего приложения
- для данного компонента есть две независимые ситуации, которые вызовут асинхронный повторный рендеринг: изменение состояния или изменение пропсов. Эти изменения могут быть сделаны в разное время, и Owl не знает, как согласовать полученные визуализации.
Вот несколько советов по работе с асинхронными компонентами:
-
Минимизируйте использование асинхронных компонентов!
-
Возможно, переместите асинхронную логику в хранилище, которое затем запускает (в основном) синхронный рендеринг.
-
Lazy
загрузка внешних библиотек — хороший пример использования асинхронного рендеринга. Это нормально, потому что мы делаем допущение, что процесс загрузки займет доли секунды и будет выполняться только один раз (см.owl.utils.loadJS
) -
Во всех остальных случаях вам поможет компонент
AsyncRoot
. Когда этот компонент встречается, создается новое дочернее дерево рендеринга, так что рендеринг этого компонента (и его дочерних элементов) не привязан к рендерингу остальной части интерфейса. Его можно использовать для асинхронного компонента, чтобы предотвратить задержку рендеринга всего интерфейса, или для синхронного, чтобы его рендеринг не задерживался другими (асинхронными) компонентами. Обратите внимание, что эта директива не влияет на первый рендеринг, а только на последующие (вызванные изменением состояния или пропсов).<div t-name="ParentComponent"> <SyncChild /> <AsyncRoot> <AsyncChild/> </AsyncRoot> </div>