Гибридные приложения

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

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

Компонент в Haunted создается с помощью функции component и стандартного класса HTMLElement. Основная структура выглядит так:

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>
      <button @click=${() => setCount(count - 1)}>Уменьшить</button>
    </div>
  `;
}

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

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

  • html используется для шаблонов с поддержкой реактивного рендеринга.
  • useState создает реактивное состояние внутри компонента.
  • События привязываются через @event=${handler}.

Реактивность и хуки

Haunted реализует концепцию реактивности через хуки, аналогично React. Основные хуки:

  • useState(initialValue) — создает локальное состояние.
  • useEffect(callback, deps) — выполняет побочные эффекты при изменении зависимостей.
  • useReducer(reducer, initialState) — управляет сложными состояниями через редьюсер.
  • useRef(initialValue) — хранит ссылку на DOM-элемент или любое значение между рендерами.

Пример использования 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>`;
}

Важно помнить: эффект выполняется после рендера, а функция очистки вызывается перед следующим вызовом или при удалении компонента.

Гибридные приложения

Haunted идеально подходит для гибридных приложений, где необходимо сочетание нативных веб-компонентов с реактивными элементами. Примеры гибридного подхода:

  • Использование Haunted внутри существующих приложений на Vanilla JS.
  • Интеграция с фреймворками типа Lit или AlpineJS для отдельных виджетов.
  • Компоненты, которые взаимодействуют с серверными данными через Fetch API или WebSocket.
Встраивание компонентов в существующий DOM

Haunted-компоненты можно монтировать динамически в любой элемент страницы:

import { component, html } from 'haunted';

function Hello({ name }) {
  return html`<p>Привет, ${name}!</p>`;
}

const HelloComponent = component(Hello);
customElements.define('hello-component', HelloComponent);

document.querySelector('#app').appendChild(document.createElement('hello-component'));
document.querySelector('hello-component').name = 'Мир';

Это позволяет постепенно внедрять реактивность в большие проекты без полной переработки существующей архитектуры.

Обработка сложных состояний

Использование useReducer удобно для гибридных приложений с большим количеством состояний:

function reducer(state, action) {
  switch(action.type) {
    case 'increment': return { count: state.count + 1 };
    case 'decrement': return { count: state.count - 1 };
    default: return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return html`
    <div>
      <p>Счётчик: ${state.count}</p>
      <button @click=${() => dispatch({ type: 'increment' })}>+</button>
      <button @click=${() => dispatch({ type: 'decrement' })}>-</button>
    </div>
  `;
}

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

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

Haunted можно использовать вместе с популярными UI-библиотеками:

  • Chart.js — рендеринг графиков через useEffect.
  • Leaflet — подключение карт с динамическими маркерами.
  • D3.js — создание сложных визуализаций без конфликтов с реактивностью Haunted.

Пример динамического графика:

function ChartComponent({ data }) {
  const chartRef = useRef(null);

  useEffect(() => {
    const chart = new Chart(chartRef.current, { type: 'bar', data });
    return () => chart.destroy();
  }, [data]);

  return html`<canvas ${ref(chartRef)}></canvas>`;
}

События и коммуникация между компонентами

Компоненты Haunted могут общаться через:

  • Кастомные события (dispatchEvent/addEventListener).
  • Общие состояния через Redux, MobX или другие менеджеры состояния.
  • Контекстные хуки, создаваемые вручную для передачи данных глубоко вниз по дереву компонентов.

Пример кастомного события:

function Child() {
  return html`<button @click=${() => this.dispatchEvent(new CustomEvent('say-hi', { bubbles: true }))}>Сказать привет</button>`;
}

function Parent() {
  useEffect(() => {
    const handleHi = (e) => console.log('Привет от дочернего компонента');
    this.addEventListener('say-hi', handleHi);
    return () => this.removeEventListener('say-hi', handleHi);
  }, []);

  return html`<child-component></child-component>`;
}

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

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

Советы по оптимизации

  • Использовать useEffect и useMemo для предотвращения лишних рендеров.
  • Минимизировать работу с DOM вне html-шаблонов.
  • Разделять компоненты на мелкие реактивные блоки для упрощения поддержки.
  • Встраивать Haunted в существующие проекты постепенно, чтобы избежать конфликтов с другими фреймворками.

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