Сравнение с useState

Haunted — это библиотека для создания веб-компонентов с реактивным состоянием на основе Web Components и Hooks, вдохновленных React. Основной инструмент для работы с состоянием — хук useState. Он позволяет компоненту хранить локальные данные и автоматически перерисовывать шаблон при их изменении.

Синтаксис useState в Haunted прост:

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

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));

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

  • useState возвращает массив из двух элементов: текущее состояние и функцию для его обновления.
  • При вызове функции обновления (setCount) компонент перерисовывается, а шаблон получает новое значение.
  • Любое изменение состояния локально для компонента и не затрагивает другие компоненты, если только не передано через свойства.

Отличия от useState в React

Хотя синтаксис похож, Haunted имеет несколько особенностей:

  1. Web Components в основе В Haunted компонент создается через customElements.define. Это означает, что компоненты полностью совместимы с нативными веб-стандартами и могут использоваться вне Haunted.

  2. Отсутствие Virtual DOM Haunted не использует виртуальный DOM. Вместо этого шаблонный тег html (из lit-html) перерисовывает только измененные части DOM. Это снижает накладные расходы и упрощает обновления.

  3. Связь с атрибутами Состояние useState не связано автоматически с атрибутами DOM, в отличие от свойств React. Для синхронизации с атрибутами необходимо использовать специальные хуки или вручную обновлять атрибуты.

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


Примеры расширенного использования useState

Сложные структуры состояния:

function TodoList() {
  const [todos, setTodos] = useState([
    { text: 'Изучить Haunted', done: false },
    { text: 'Написать статью', done: false }
  ]);

  const toggleTodo = index => {
    const newTodos = todos.map((todo, i) =>
      i === index ? { ...todo, done: !todo.done } : todo
    );
    setTodos(newTodos);
  };

  return html`
    <ul>
      ${todos.map(
        (todo, index) => html`
          <li>
            <input
              type="checkbox"
              .checked=${todo.done}
              @change=${() => toggleTodo(index)}
            />
            ${todo.text}
          </li>
        `
      )}
    </ul>
  `;
}

customElements.define('todo-list', component(TodoList));

Особенности:

  • Для сложных объектов необходимо создавать новый объект/массив при изменении, чтобы Haunted корректно отследил обновление.
  • Прямое изменение существующих объектов (todos[0].done = true) не вызовет перерисовку.

Связь useState и других хуков

Haunted поддерживает комбинацию хуков, аналогичных React:

  • useEffect — реакция на изменения состояния.
  • useReducer — управление сложными состояниями через редуктор.
  • useRef — доступ к DOM-элементам без повторной перерисовки.

Пример использования useState с useEffect:

function Timer() {
  const [seconds, setSeconds] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setSeconds(s => s + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return html`<p>Прошло секунд: ${seconds}</p>`;
}

customElements.define('timer-counter', component(Timer));

Особенности:

  • useEffect с пустым массивом зависимостей выполняется один раз при монтировании компонента.
  • Функция очистки позволяет остановить таймер при удалении компонента из DOM.

Оптимизация состояния в Haunted

  1. Минимизация количества состояний Создание большого числа useState для каждого поля может усложнить код. Лучше объединять связанные данные в один объект состояния.

  2. Мемоизация вычислений Для дорогостоящих вычислений используется useMemo, чтобы не пересчитывать результат на каждом обновлении состояния.

  3. Ленивая инициализация useState принимает функцию для инициализации состояния, если оно сложное:

const [data, setData] = useState(() => fetchInitialData());

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


Отличия поведения Haunted и React при обновлениях

Характеристика Haunted React
Обновление DOM Через lit-html, без виртуального DOM Через виртуальный DOM
Связь с атрибутами Не автоматическая Частично через props
Монтирование компонентов Web Components native Виртуальный DOM
Состояние сложных объектов Требуется копирование объектов Может использовать иммутабельные подходы, но чаще тоже копирование
Локальное состояние Только в компоненте Только в компоненте

Haunted useState обеспечивает знакомый интерфейс React, но с упором на Web Components и прямую работу с DOM. Понимание этих различий критично для написания эффективных компонентов с реактивным поведением.