Пользовательские директивы¶
Директивы — это функции, которые могут расширять Lit, настраивая отображение шаблонного выражения. Директивы полезны и мощны, потому что они могут иметь состояние, обращаться к DOM, получать уведомления об отключении и повторном подключении шаблонов, а также самостоятельно обновлять выражения вне вызова рендеринга.
Использовать директиву в шаблоне так же просто, как вызвать функцию в выражении шаблона:
1 |
|
Lit поставляется с рядом встроенных директив, таких как repeat()
и cache()
. Пользователи также могут писать свои собственные директивы.
Существует два вида директив:
- Простые функции
- Директивы, основанные на классах
Простая функция возвращает значение для рендеринга. Она может принимать любое количество аргументов или не принимать их вовсе.
1 |
|
Директива, основанная на классе, позволяет делать то, что не под силу простой функции. Используйте директивы на основе классов, чтобы:
- Получить прямой доступ к рендерингу DOM (например, добавлять, удалять или переупорядочивать рендеринговые узлы DOM).
- Сохранять состояние между рендерами.
- Обновлять DOM асинхронно, вне вызова рендера.
- Очистка ресурсов при отключении директивы от DOM.
Остальная часть этой страницы описывает директивы на основе классов.
Создание директив на основе классов¶
Чтобы создать директиву на основе класса, выполните следующие действия:
- Реализуйте директиву в виде класса, который расширяет класс
Directive
. - Передайте свой класс фабрике
directive()
, чтобы создать функцию директивы, которую можно использовать в выражениях шаблона Lit.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Когда этот шаблон обрабатывается, директива function (hello()
) возвращает объект DirectiveResult
, который дает указание Lit создать или обновить экземпляр директивы class (HelloDirective
). Затем Lit вызывает методы экземпляра директивы для выполнения логики обновления.
Некоторые директивы должны обновлять DOM асинхронно, вне обычного цикла обновления. Чтобы создать асинхронную директиву, расширьте базовый класс AsyncDirective
вместо Directive
. Подробности см. в разделе Асинхронные директивы.
Жизненный цикл директивы, основанной на классе¶
Класс директив имеет несколько встроенных методов жизненного цикла:
- Конструктор класса, для однократной инициализации.
render()
, для декларативного рендеринга.update()
, для императивного доступа к DOM.
Вы должны реализовать обратный вызов render()
для всех директив. Реализация update()
необязательна. Реализация по умолчанию update()
вызывает и возвращает значение из render()
.
Директивы Async, которые могут обновлять DOM вне обычного цикла обновления, используют некоторые дополнительные обратные вызовы жизненного цикла. Подробности см. в разделе Async-директивы.
Одноразовая установка: constructor()¶
Когда Lit впервые встречает DirectiveResult
в выражении, он создаст экземпляр соответствующего класса директивы (вызывая конструктор директивы и инициализаторы любых полей класса):
1 2 3 4 5 6 7 8 9 10 11 12 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Пока одна и та же функция директивы используется в одном и том же выражении при каждом рендере, предыдущий экземпляр используется повторно, таким образом, состояние экземпляра сохраняется между рендерами.
Конструктор получает один объект PartInfo
, который предоставляет метаданные о выражении, в котором была использована директива. Это может быть полезно для проверки ошибок в случаях, когда директива предназначена для использования только в определенных типах выражений (см. Ограничение директивы одним типом выражения).
Декларативный рендеринг: render()¶
Метод render()
должен возвращать значение для рендеринга в DOM. Он может возвращать любое рендерируемое значение, включая другой DirectiveResult
.
Помимо обращения к состоянию экземпляра директивы, метод render()
может также принимать произвольные аргументы, передаваемые в функцию директивы:
1 2 3 |
|
Параметры, заданные для метода render()
, определяют сигнатуру функции директивы:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Императивный доступ к DOM: update()¶
В более продвинутых случаях использования вашей директиве может потребоваться доступ к базовому DOM и императивное чтение из него или его изменение. Этого можно добиться, переопределив обратный вызов update()
.
Обратный вызов update()
получает два аргумента:
- Объект
Part
с API для прямого управления DOM, связанным с выражением. - Массив, содержащий аргументы
render()
.
Ваш метод update()
должен возвращать что-то, что Lit может отрендерить, или специальное значение noChange
, если повторный рендеринг не требуется. Обратный вызов update()
довольно гибкий, но типичные варианты использования включают в себя:
- Чтение данных из DOM и использование их для генерации значения для рендеринга.
- Императивное обновление DOM с помощью ссылки
element
илиparentNode
на объектPart
. В этом случаеupdate()
обычно возвращаетnoChange
, указывая, что Lit не нужно предпринимать никаких дальнейших действий для рендеринга директивы.
Части¶
Каждая позиция выражения имеет свой собственный объект Part
:
ChildPart
для выражений в позиции HTML child.AttributePart
для выражений в позиции значения атрибута HTML.BooleanAttributePart
для выражений в значении булевого атрибута (имя с префиксом?
).EventPart
для выражений в позиции слушателя события (имя с префиксом@
).PropertyPart
для выражений в позиции значения свойства (имя с префиксом.
).ElementPart
для выражений на теге элемента.
В дополнение к метаданным, содержащимся в PartInfo
, все типы Part
предоставляют доступ к DOM элементу
, связанному с выражением (или parentNode
, в случае ChildPart
), к которому можно напрямую обратиться в update()
. Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Кроме того, модуль directive-helpers.js
содержит ряд вспомогательных функций, которые работают с объектами Part
и могут быть использованы для динамического создания, вставки и перемещения частей внутри ChildPart
директивы.
Вызов render()
из update()
¶
Реализация по умолчанию update()
просто вызывает и возвращает значение из render()
. Если вы переопределили update()
и все еще хотите вызвать render()
для генерации значения, вам нужно вызвать render()
явно.
Аргументы render()
передаются в update()
в виде массива. Вы можете передать аргументы в render()
следующим образом:
1 2 3 4 5 6 7 |
|
1 2 3 4 5 6 7 |
|
Различия между update()
и render()
¶
Хотя обратный вызов update()
является более мощным, чем обратный вызов render()
, есть важное различие: При использовании пакета @lit-labs/ssr
для рендеринга на стороне сервера (SSR), на сервере вызывается только метод render()
. Чтобы быть совместимыми с SSR, директивы должны возвращать значения из render()
и использовать update()
только для логики, требующей доступа к DOM.
Сигнализация отсутствия изменений¶
Иногда в директиве может не быть ничего нового для рендеринга Lit. Вы сигнализируете об этом, возвращая noChange
из метода update()
или render()
. Это отличается от возврата undefined
, который заставляет Lit очистить Part
, связанную с директивой. Возврат noChange
оставляет на месте ранее отрендеренное значение.
Существует несколько распространенных причин для возврата noChange
:
- Исходя из входных значений, нет ничего нового для рендеринга.
- Метод
update()
в обязательном порядке обновил DOM. - В асинхронной директиве вызов
update()
илиrender()
может вернутьnoChange
, потому что рендерить пока нечего.
Например, директива может отслеживать предыдущие значения, переданные ей, и выполнять собственную грязную проверку, чтобы определить, нуждается ли вывод директивы в обновлении. Метод update()
или render()
может возвращать noChange
, чтобы сигнализировать, что вывод директивы не нуждается в повторном рендеринге.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Ограничение директивы одним типом выражения¶
Некоторые директивы полезны только в одном контексте, например, в выражении атрибута или дочернем выражении. Если директива помещена в неправильный контекст, она должна выдать соответствующую ошибку.
Например, директива classMap
проверяет, что она используется только в AttributePart
и только для class
атрибута:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
Асинхронные директивы¶
Предыдущие примеры директив являются синхронными: они синхронно возвращают значения из своих обратных вызовов render()
/ update()
, поэтому их результаты записываются в DOM во время обратного вызова update()
компонента.
Иногда требуется, чтобы директива могла обновлять DOM асинхронно — например, если она зависит от асинхронного события, такого как сетевой запрос.
Для асинхронного обновления результата директивы необходимо расширить базовый класс AsyncDirective
, который предоставляет API setValue()
. setValue()
позволяет директиве "подставить" новое значение в выражение шаблона, вне обычного цикла update
/render
шаблона.
Вот пример простой асинхронной директивы, которая выдает значение Promise:
1 2 3 4 5 6 7 8 9 10 11 |
|
1 2 3 4 5 6 7 8 9 10 11 |
|
Здесь отрисованный шаблон показывает "Waiting for promise to resolve", за которым следует разрешенное значение обещания, когда бы оно ни разрешилось.
Асинхронные директивы часто нуждаются в подписке на внешние ресурсы. Чтобы предотвратить утечку памяти, асинхронные директивы должны отменять подписку или избавляться от ресурсов, когда экземпляр директивы больше не используется. Для этого AsyncDirective
предоставляет следующие дополнительные обратные вызовы и API жизненного цикла:
-
disconnected()
: Вызывается, когда директива больше не используется. Экземпляры директив отсоединяются в трех случаях:- Когда дерево DOM, в котором содержится директива, удаляется из DOM
- Когда основной элемент директивы отключается
- Когда выражение, породившее директиву, больше не разрешается в ту же директиву.
После того как директива получает обратный вызов
disconnected
, она должна освободить все ресурсы, на которые она подписалась во времяupdate
илиrender
, чтобы предотвратить утечку памяти. -
reconnected()
: Вызывается, когда ранее отключенная директива возвращается к использованию. Поскольку поддеревья DOM могут быть временно отключены, а затем снова подключены, отключенной директиве может потребоваться реакция на повторное подключение. Примерами этого могут быть случаи, когда DOM удаляется и кэшируется для последующего использования, или когда элемент хоста перемещается, вызывая отключение и повторное подключение. Обратный вызовreconnected()
всегда должен быть реализован наряду сdisconnected()
, чтобы вернуть отключенную директиву в ее рабочее состояние. -
isConnected
: Отражает текущее состояние подключения директивы.
Обратите внимание, что AsyncDirective
может продолжать получать обновления, пока он отключен, если содержащее его дерево будет перерисовано. В связи с этим, update
и/или render
должны всегда проверять флаг this.isConnected
перед подпиской на любые долго хранящиеся ресурсы, чтобы предотвратить утечки памяти.
Ниже приведен пример директивы, которая подписывается на Observable
и обрабатывает отключение и повторное подключение соответствующим образом:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
|