Создание пользовательских директив

Lit предоставляет мощный механизм для работы с шаблонами через директивы, которые позволяют динамически изменять поведение рендеринга без необходимости модифицировать сам компонент. Директивы представляют собой функции или классы, управляющие частью DOM, и применяются внутри шаблонов, создаваемых с помощью html или svg. Создание собственных директив расширяет возможности фреймворка и позволяет реализовать сложные сценарии обновления DOM.


Принцип работы директив

Директива в Lit — это объект, который контролирует рендеринг конкретного узла шаблона. В момент первого рендера директива получает ссылку на узел DOM и контейнер, в который этот узел вставлен. При последующих обновлениях директива вызывается с новыми значениями, что позволяет:

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

Ключевой момент: директивы работают на уровне слабой связки с компонентом, а не изменяют его напрямую. Это обеспечивает высокую производительность и предсказуемость обновлений.


Типы директив

В Lit существует два основных типа директив:

  1. Функциональные директивы Представляют собой функции, которые возвращают класс-наследник Directive. Их преимущество — простота и минимальный объем кода. Они идеально подходят для одноразового изменения узла.

  2. Классовые директивы Более гибкий вариант. Создается класс, наследующий Directive, и реализуются методы render и update. Используются, когда требуется хранить внутреннее состояние между вызовами.


Создание функциональной директивы

Простейший пример — директива, добавляющая CSS-класс к элементу, если условие истинно:

import { directive, NodePart } from 'lit/directive.js';

const conditionalClass = directive((className, condition) => (part) => {
  if (!(part instanceof NodePart)) return;
  if (condition) {
    part.startNode.parentElement.classList.add(className);
  } else {
    part.startNode.parentElement.classList.remove(className);
  }
});

Использование внутри шаблона:

html`<div ${conditionalClass('active', isActive)}>Контент</div>`

Преимущество функционального подхода — лаконичность. Недостаток — невозможность легко хранить сложное состояние между обновлениями.


Создание классовой директивы

Для более сложных сценариев используется классовая директива. Она наследует Directive и реализует метод update, который вызывается при каждом обновлении:

import { directive, Directive, PartType } from 'lit/directive.js';

class HighlightDirective extends Directive {
  constructor(partInfo) {
    super(partInfo);
    if (partInfo.type !== PartType.CHILD) {
      throw new Error('HighlightDirective можно использовать только с текстовыми узлами');
    }
  }

  render(text) {
    return `<span style="background-color: yellow">${text}</span>`;
  }

  update(part, [text]) {
    part.setValue(this.render(text));
    return part.commit();
  }
}

export const highlight = directive(HighlightDirective);

Применение в шаблоне:

html`<p>${highlight('Важный текст')}</p>`

Особенности классовых директив:

  • Хранение внутреннего состояния между вызовами (this.someState).
  • Возможность проверки типа узла или контейнера перед вставкой.
  • Гибкость управления жизненным циклом узла.

Передача параметров и реактивность

Директивы принимают параметры, передаваемые как массив аргументов. При обновлении шаблона Lit автоматически вызывает директиву с новыми значениями. Чтобы избежать лишних рендеров, внутри директивы можно хранить предыдущие значения и сравнивать их с текущими:

class ToggleDirective extends Directive {
  previousValue;

  render(value) {
    return value ? 'Включено' : 'Выключено';
  }

  update(part, [value]) {
    if (value === this.previousValue) return part;
    this.previousValue = value;
    return part.setValue(this.render(value));
  }
}

export const toggle = directive(ToggleDirective);

Такой подход позволяет реализовать эффективное управление DOM без перерисовки всего узла.


Использование директив для управления DOM

Директивы часто применяются для:

  • условного отображения элементов;
  • ленивой загрузки контента;
  • управления списками и таблицами;
  • подключения внешних библиотек для отдельных узлов.

Пример условного рендеринга:

import { ifDefined } from 'lit/directives/if-defined.js';

html`<img src="${ifDefined(imageUrl)}" alt="Картинка">`

Здесь ifDefined — встроенная директива, которая удаляет атрибут, если значение undefined, что позволяет избежать появления пустых атрибутов в DOM.


Практические рекомендации

  • Использовать функциональные директивы для простых изменений DOM.
  • Классовые директивы применять, когда требуется хранение состояния между обновлениями.
  • Проверять тип узла с помощью partInfo.type, чтобы избежать ошибок при применении к неподходящим частям шаблона.
  • Минимизировать количество изменений DOM внутри директивы — предпочтительно изменять только необходимые узлы.
  • Сравнивать новые и предыдущие значения параметров для оптимизации производительности.

Встроенные возможности Lit для расширения

Lit предоставляет готовые базовые директивы, такие как repeat, asyncAppend, asyncReplace и ifDefined. Создание пользовательских директив позволяет сочетать эти встроенные механизмы с кастомной логикой, создавая высокоэффективные, реактивные интерфейсы без потери производительности.

Понимание принципов работы директив — ключ к построению масштабируемых и поддерживаемых приложений на Lit, где каждая часть шаблона управляется максимально эффективно и предсказуемо.