Встроенные директивы

Haunted — это библиотека для создания веб-компонентов на основе хуков в стиле React. Одной из ключевых особенностей является система встроенных директив, позволяющая управлять поведением шаблонов в процессе рендеринга. Директивы — это функции, которые возвращают специальные значения для шаблона, изменяя стандартное поведение Lit или html-шаблонов.


Основные концепции

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

  • Управления атрибутами элементов.
  • Создания условного рендеринга.
  • Оптимизации повторных обновлений.

Директивы в Haunted используют функцию directive из пакета haunted/directive. Простейшая форма выглядит так:

import { html, render } from 'haunted';
import { directive } from 'haunted/directive';

const myDirective = directive((value) => (part) => {
  part.setValue(`Значение: ${value}`);
});

render(html`<div>${myDirective('Тест')}</div>`, document.body);

Здесь part — это объект, представляющий участок DOM, который директива может изменять. Метод setValue позволяет напрямую менять содержимое элемента без повторного рендеринга всего шаблона.


Директивы для условного рендеринга

Haunted не содержит полноценного JSX-синтаксиса, но встроенные директивы позволяют реализовать условное отображение. Например:

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

const userName = null;

html`<p>Имя: ${ifDefined(userName)}</p>`;

ifDefined предотвращает рендеринг undefined и null как строки "undefined" или "null". Это особенно важно при работе с динамическими данными.

Для более сложного условного рендеринга можно использовать пользовательские директивы:

import { directive } from 'haunted/directive';

const showIf = directive((condition, template) => (part) => {
  if (condition) {
    part.setValue(template);
  } else {
    part.setValue('');
  }
});

html`${showIf(true, html`<p>Отображается только если условие истинно</p>`)}`

Директивы для списков

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

import { repeat } from 'haunted/directives/repeat.js';

const items = ['яблоко', 'банан', 'вишня'];

html`
<ul>
  ${repeat(items, (item) => item, (item, index) => html`<li>${item}</li>`)}
</ul>
`;

Параметры repeat:

  • Первый аргумент — массив данных.
  • Второй аргумент — функция генерации ключа для каждого элемента (оптимизация повторного рендеринга).
  • Третий аргумент — функция рендеринга элемента.

Такой подход минимизирует манипуляции с DOM и повышает производительность.


Атрибутные директивы

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

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

const href = null;

html`<a href="${ifDefined(href)}">Ссылка</a>`;

Использование ifDefined гарантирует, что атрибут href будет полностью отсутствовать, если значение не определено, а не будет отображаться как "null".


Кастомные директивы и их жизненный цикл

Создание кастомной директивы позволяет внедрять собственную логику обновления DOM:

import { directive } from 'haunted/directive';

const highlight = directive((color) => (part) => {
  part.startNode.parentNode.style.backgroundColor = color;
});

html`<p>${highlight('yellow')}Выделенный текст</p>`;

Жизненный цикл кастомной директивы ограничен обновлениями соответствующего part. Это означает:

  1. Директива вызывается при первичной отрисовке.
  2. При последующих обновлениях она вызывается заново, только если значение изменилось.
  3. Можно реализовать чистку ресурсов, если директива управляет внешними эффектами, создавая логику в замыканиях.

Совмещение нескольких директив

В Haunted можно комбинировать директивы, создавая цепочки обработки шаблонов. Например, условное рендеринг + повторение:

import { repeat } from 'haunted/directives/repeat.js';
import { directive } from 'haunted/directive';

const showList = directive((condition, items) => (part) => {
  if (!condition) {
    part.setValue('');
    return;
  }
  part.setValue(html`
    <ul>
      ${repeat(items, (item) => item.id, (item) => html`<li>${item.name}</li>`)}
    </ul>
  `);
});

html`${showList(true, [{ id: 1, name: 'Первый' }, { id: 2, name: 'Второй' }])}`;

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


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

  • Использовать директивы для управления DOM, а не для бизнес-логики.
  • Для списков всегда применять ключи (repeat или кастомная директива с ключами) для минимизации перерисовки.
  • Для атрибутов использовать ifDefined, чтобы избегать некорректного рендеринга null и undefined.
  • Кастомные директивы должны быть чистыми функциями, избегая побочных эффектов вне part.

Haunted встроенные директивы позволяют создавать динамичные, оптимизированные и легко управляемые шаблоны. Они объединяют гибкость функциональных компонентов с высокой производительностью DOM-операций, делая работу с веб-компонентами более предсказуемой и управляемой.