Создание собственных директив

В библиотеке Haunted директивы предоставляют механизм для управления поведением шаблонов и элементов DOM без необходимости напрямую вмешиваться в жизненный цикл компонентов. Директива — это функция, которая возвращает объект с методами update и render, определяющими, как изменяется содержимое элемента при обновлении состояния.

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

import { directive } from 'haunted';

const myDirective = directive((value) => {
  return {
    render() {
      return `<span>${value}</span>`;
    },
    update(part, [newValue]) {
      if (part.value !== newValue) {
        part.setValue(newValue);
      }
    }
  };
});

Ключевые моменты:

  • render() возвращает результат рендера, который будет вставлен в DOM.
  • update(part, args) вызывается при обновлении значений, где part — это участок DOM, который нужно изменить, а args — новые аргументы директивы.
  • directive создает фабрику директив, которая может быть повторно использована в разных компонентах.

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

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

import { html } from 'haunted';

const template = html`<div>${myDirective('Привет')}</div>`;

В этом примере myDirective управляет содержимым <div>, динамически обновляя его при изменении переданного значения.


Доступ к part и управление DOM

Параметр part предоставляет низкоуровневый доступ к DOM. Через него можно управлять:

  • текстовым содержимым (part.setValue)
  • атрибутами (part.commit())
  • вставкой элементов (part.setConnected и part.setChildNodes)

Пример директивы, которая изменяет цвет текста на основе состояния:

const colorDirective = directive((color) => {
  return (part) => {
    part.setValue(`<span style="color: ${color}">Цветной текст</span>`);
  };
});

Параметры и аргументы директив

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

const multiArgDirective = directive((text, fontSize, color) => (part) => {
  part.setValue(
    `<span style="font-size:${fontSize}px; color:${color}">${text}</span>`
  );
});

Использование:

html`<div>${multiArgDirective('Привет', 20, 'blue')}</div>`

Контроль рендеринга и оптимизация

Директивы позволяют оптимизировать рендеринг, обновляя только те части DOM, которые изменились. Метод update проверяет изменения значений и предотвращает лишние пересоздания элементов:

const efficientDirective = directive((text) => ({
  render() {
    return text;
  },
  update(part, [newText]) {
    if (part.value !== newText) {
      part.setValue(newText);
    }
  }
}));

Такой подход снижает нагрузку на браузер при частых обновлениях состояния.


Работа с асинхронными данными

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

const asyncDirective = directive((promise) => {
  return async (part) => {
    part.setValue('Загрузка...');
    const result = await promise;
    part.setValue(result);
  };
});

Это позволяет отображать промежуточные состояния и динамически подставлять результат в шаблон без полного перерендера компонента.


Комбинирование директив

Несколько директив можно использовать совместно для сложных эффектов. При этом каждая директива управляет своим участком DOM, а библиотека обеспечивает правильное связывание и обновление:

html`
  <div>
    ${colorDirective('red')}
    ${multiArgDirective('Привет', 18, 'green')}
  </div>
`

Практические советы

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

Примеры распространенных сценариев

  1. Условный рендеринг:
const ifDirective = directive((condition, content) => (part) => {
  part.setValue(condition ? content : '');
});
  1. Стилизация и анимации:
const animateDirective = directive((duration) => (part) => {
  part.setValue(`<div style="transition: all ${duration}s">Анимация</div>`);
});
  1. Фильтрация контента:
const filterDirective = directive((items, filterFn) => (part) => {
  const filtered = items.filter(filterFn).join(', ');
  part.setValue(filtered);
});

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