Покрытие кода

Haunted — это небольшая библиотека для создания веб-компонентов на базе стандартного Custom Elements API с реактивным подходом, схожим с React Hooks. Основная цель — обеспечить простое управление состоянием компонента, реактивное обновление DOM и интеграцию с нативными браузерными возможностями без необходимости подключения сложных фреймворков.

Библиотека использует hooks для управления состоянием, эффектах и других реактивных процессов. Основные элементы Haunted:

  • component — функция для создания компонента.
  • html — шаблонная функция для описания разметки.
  • useState — реактивное состояние компонента.
  • useEffect — побочные эффекты.
  • useRef — ссылка на DOM-элемент или любое значение, сохраняющееся между рендерами.

Создание компонента

Компоненты в Haunted создаются через функцию component. Пример:

import { component, html, 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 создает реактивное состояние. Любое изменение автоматически вызывает повторный рендер компонента.
  • События (@click) привязываются через синтаксис Lit-html (@event=${handler}), что обеспечивает корректное обновление обработчиков при ререндере.
  • component(Counter) превращает обычную функцию в полноценный custom element.

Реактивное состояние и hooks

Haunted строится вокруг концепции реактивного состояния:

useState

Используется для хранения данных, которые изменяются со временем.

const [value, setValue] = useState(initialValue);
  • value — текущее состояние.
  • setValue — функция обновления состояния.
  • При вызове setValue Haunted автоматически перерисовывает компонент.

useEffect

Позволяет выполнять побочные эффекты после рендера:

useEffect(() => {
  console.log('Компонент отрендерен');
  return () => console.log('Очистка эффекта при размонтировании');
}, [value]);
  • Второй аргумент — массив зависимостей. Эффект сработает только при изменении этих значений.
  • Возвращаемая функция используется для очистки (аналог componentWillUnmount в React).

useRef

Сохраняет ссылку на DOM-элемент или любое значение:

const inputRef = useRef();
return html`<input type="text" ${ref(inputRef)} />`;
  • Значение сохраняется между рендерами.
  • Полезно для прямой работы с DOM без ререндеринга.

Работа с шаблонами

Haunted использует html из lit-html, что позволяет писать декларативные шаблоны. Основные возможности:

  • Интерполяция данных: ${variable}.
  • Условный рендеринг: через тернарные операторы.
return html`
  <div>
    ${isLoggedIn ? html`<p>Добро пожаловать!</p>` : html`<p>Вход не выполнен</p>`}
  </div>
`;
  • Списки: через map:
return html`
  <ul>
    ${items.map(item => html`<li>${item}</li>`)}
  </ul>
`;

Комбинирование нескольких hooks

Haunted позволяет объединять несколько хуков в одном компоненте. Например:

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

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

  return html`<p>Прошло секунд: ${seconds}</p>`;
}
  • Одновременное использование useState и useEffect позволяет строить полноценную реактивную логику.
  • Очистка ресурсов (таймеров, подписок) обеспечивается через возвращаемую функцию useEffect.

Управление атрибутами и свойствами

Custom Elements поддерживают связь атрибутов с состоянием. Haunted интегрируется с этим через props:

function Greeting({ name = 'Гость' }) {
  return html`<p>Привет, ${name}!</p>`;
}

customElements.define('my-greeting', component(Greeting));
  • Любой переданный атрибут автоматически становится пропсом.
  • Можно устанавливать дефолтные значения через деструктуризацию { name = 'Гость' }.

Сложные сценарии

Вложенные компоненты

Haunted поддерживает вложенные компоненты без проблем с ререндером:

function Parent() {
  return html`<my-counter></my-counter>`;
}

customElements.define('my-parent', component(Parent));

Подписки на события

Компоненты могут подписываться на внешние события, используя useEffect:

useEffect(() => {
  function handleResize() {
    console.log(window.innerWidth);
  }
  window.addEventListener('resize', handleResize);
  return () => window.removeEventListener('resize', handleResize);
}, []);
  • Гарантирует автоматическую очистку при удалении компонента из DOM.

Оптимизация рендеринга

Haunted рендерит компонент только при изменении состояния. Чтобы избежать лишних ререндеров:

  • Не создавать новые функции внутри шаблона без необходимости.
  • Использовать useMemo (при необходимости) для вычислений, которые не зависят от частых изменений состояния.
import { useMemo } from 'haunted';

const doubled = useMemo(() => count * 2, [count]);
  • useMemo кеширует результат между рендерами, если зависимости не изменились.

Стилизация компонентов

Haunted поддерживает scoped CSS через шаблонные строки:

return html`
  <style>
    p { color: red; font-weight: bold; }
  </style>
  <p>Красный текст</p>
`;
  • Стили, объявленные внутри шаблона, применяются только к текущему компоненту.
  • Можно комбинировать с CSS-переменными и CSS-модулями.

Интеграция с внешними библиотеками

Haunted легко интегрируется с сторонними библиотеками:

  • Для UI-библиотек достаточно обернуть их вызовы в useEffect.
  • Для глобального состояния можно использовать контекст или внешние store (например, Redux), подписываясь через хуки.
useEffect(() => {
  const unsubscribe = store.subscribe(() => setState(store.getState()));
  return () => unsubscribe();
}, []);
  • Это обеспечивает реактивное обновление Haunted-компонента при изменении глобального состояния.

Haunted сочетает легковесность, реактивность и полное использование нативных возможностей браузера, позволяя строить как простые виджеты, так и сложные SPA без тяжеловесных фреймворков. Она опирается на hooks, шаблонные строки и Custom Elements, обеспечивая современный подход к созданию компонентов.