Рендеринг иlit-html

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

В основе lit-html лежит концепция шаблонных литералов JavaScript, где HTML-код оформляется внутри функции html и может содержать динамические выражения.

import { html, render } from 'lit-html';

const template = (name) => html`
  <div>
    <h1>Привет, ${name}!</h1>
    <p>Добро пожаловать в Haunted и lit-html.</p>
  </div>
`;

render(template('Мир'), document.body);

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

  • html создаёт шаблонные строки, которые internaly превращаются в дерево DOM-подобных узлов.
  • Выражения ${...} вставляются динамически, и lit-html обновляет только изменённые узлы, минимизируя операции над DOM.
  • Функция render(templateResult, container) отвечает за монтирование шаблона в указанный DOM-элемент и его последующие обновления.

Интеграция с Haunted

В Haunted компоненты определяются как функции, которые возвращают шаблон lit-html. Состояние компонента управляется через хуки useState, useEffect и другие.

import { component, useState } from 'haunted';
import { html } from 'lit-html';

function Counter() {
  const [count, setCount] = useState(0);

  return html`
    <div>
      <p>Счётчик: ${count}</p>
      <button @click=${() => setCount(count + 1)}>Увеличить</button>
    </div>
  `;
}

customElements.define('my-counter', component(Counter));

Разбор примера:

  • Компонент Counter — обычная функция, возвращающая шаблон html.
  • Хук useState создаёт реактивное состояние. При вызове setCount Haunted повторно рендерит компонент.
  • События привязываются с помощью синтаксиса @event=${handler}. Lit-html обеспечивает корректное управление слушателями, предотвращая утечки памяти.

Динамические списки и условный рендеринг

Lit-html поддерживает рендеринг массивов и условные конструкции:

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

const listTemplate = html`
  <ul>
    ${items.map(item => html`<li>${item}</li>`)}
  </ul>
`;

render(listTemplate, document.body);
  • Использование Array.map позволяет создавать повторяющиеся элементы без ручного управления DOM.
  • Lit-html отслеживает позиции элементов и обновляет только изменённые узлы при последующих рендерах.

Условный рендеринг выполняется через тернарные операторы:

const loggedIn = true;

const loginTemplate = html`
  <div>
    ${loggedIn ? html`<p>Добро пожаловать!</p>` : html`<button>Войти</button>`}
  </div>
`;

render(loginTemplate, document.body);
  • Важно, что оба ветвления должны быть обёрнуты в `html```, чтобы lit-html корректно управлял DOM-узлами.

Эффективность обновлений DOM

Lit-html использует концепцию шаблонных инструкций, которые минимизируют манипуляции с DOM:

  1. Замена текста и атрибутов — изменяется только содержимое узла, а не весь элемент.
  2. Обновление массивов — элементы списка переиспользуются, новые создаются только при необходимости.
  3. События — lit-html автоматически удаляет старые слушатели и добавляет новые при рендере.
let count = 0;
const container = document.body;

function update() {
  render(html`<p>Счётчик: ${count}</p>`, container);
}

setInterval(() => {
  count++;
  update();
}, 1000);
  • В этом примере каждый рендер обновляет только текст в <p>, не трогая остальные узлы, что критически важно для производительности.

Динамические атрибуты и свойства

Lit-html позволяет безопасно задавать атрибуты и свойства элементов:

const isDisabled = true;

const buttonTemplate = html`
  <button ?disabled=${isDisabled}>Нажми меня</button>
`;

render(buttonTemplate, document.body);
  • Префикс ? используется для булевых атрибутов.
  • Префикс . применяется для прямого присвоения свойства элемента: <input .value=${inputValue}>.
  • Префикс @ используется для событий: <button @click=${handleClick}>.

Использование условных шаблонов и повторного рендеринга в Haunted

Комбинация Haunted и lit-html позволяет создавать высоко динамичные интерфейсы. Пример реактивного списка с состоянием:

import { component, useState } from 'haunted';
import { html } from 'lit-html';

function TodoList() {
  const [todos, setTodos] = useState(['Сделать домашку', 'Купить продукты']);
  const [newTodo, setNewTodo] = useState('');

  const addTodo = () => {
    if(newTodo.trim()) {
      setTodos([...todos, newTodo]);
      setNewTodo('');
    }
  };

  return html`
    <div>
      <input .value=${newTodo} @input=${e => setNewTodo(e.target.value)} placeholder="Новая задача" />
      <button @click=${addTodo}>Добавить</button>
      <ul>
        ${todos.map(todo => html`<li>${todo}</li>`)}
      </ul>
    </div>
  `;
}

customElements.define('todo-list', component(TodoList));
  • Изменение состояния через setTodos или setNewTodo инициирует повторный рендер.
  • Lit-html обновляет только изменившиеся элементы списка, не перестраивая весь DOM.
  • Обработка событий с привязкой к функции обеспечивает реактивность компонентов без утечек памяти.

Итоговые принципы работы

  1. Декларативное создание DOM с использованием шаблонов html.
  2. Минимизация операций над DOM благодаря умной дифференциации изменений.
  3. Реактивное состояние компонентов через хуки Haunted.
  4. Чистая интеграция событий, атрибутов и свойств.
  5. Простота управления массивами и условным рендерингом с сохранением производительности.

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