UI-киты на Haunted

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

Функциональные компоненты в Haunted создаются с помощью функции component или html из библиотеки haunted. Пример минимального компонента:

import { html, component } from 'haunted';

function MyButton({ label }) {
  return html`<button>${label}</button>`;
}

customElements.define('my-button', component(MyButton));

Здесь html используется для декларативного описания DOM, а component превращает функцию в полноценный кастомный элемент.

Хуки Haunted для UI

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

  • useState — хранение локального состояния компонента:
import { useState } from 'haunted';

function Counter() {
  const [count, setCount] = useState(0);
  return html`
    <div>
      <span>${count}</span>
      <button @click=${() => setCount(count + 1)}>Увеличить</button>
    </div>
  `;
}
  • useEffect — выполнение побочных эффектов:
import { useEffect } from 'haunted';

useEffect(() => {
  console.log('Компонент был обновлен или создан');
}, [count]);
  • useRef — получение прямого доступа к DOM-элементу:
import { useRef, useEffect } from 'haunted';

function InputFocus() {
  const inputRef = useRef();
  useEffect(() => {
    inputRef.current.focus();
  }, []);
  return html`<input ref=${inputRef} />`;
}

Структура UI-кита

UI-кит на Haunted строится вокруг переиспользуемых компонентов с управляемым состоянием и чистым API. Основные принципы организации:

  1. Модульность — каждый компонент хранится в отдельном файле.
  2. Стилизация через CSS-in-JS или Shadow DOM, что изолирует стили от глобальных.
  3. Параметризация через свойства (props) — позволяет настраивать внешний вид и поведение.
  4. Реактивность — компоненты автоматически перерисовываются при изменении состояния.

Пример базовой структуры:

ui-kit/
 ├─ button/
 │   ├─ button.js
 │   └─ button.css
 ├─ modal/
 │   ├─ modal.js
 │   └─ modal.css
 └─ index.js

Пример продвинутого компонента UI-кита

Компонент модального окна с управлением видимостью через состояние:

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

function Modal({ open = false, onClose }) {
  const [isOpen, setIsOpen] = useState(open);

  useEffect(() => {
    setIsOpen(open);
  }, [open]);

  const close = () => {
    setIsOpen(false);
    if (onClose) onClose();
  };

  return html`
    <div class="overlay" style="display: ${isOpen ? 'block' : 'none'}" @click=${close}>
      <div class="modal" @click=${e => e.stopPropagation()}>
        <slot></slot>
        <button @click=${close}>Закрыть</button>
      </div>
    </div>
    <style>
      .overlay {
        position: fixed;
        top: 0; left: 0;
        width: 100%; height: 100%;
        background: rgba(0,0,0,0.5);
        display: flex;
        align-items: center;
        justify-content: center;
      }
      .modal {
        background: white;
        padding: 1rem;
        border-radius: 0.5rem;
      }
    </style>
  `;
}

customElements.define('my-modal', component(Modal));

Паттерны взаимодействия компонентов

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

  • Композиция через слоты:
html`<my-modal>
       <p>Содержимое модального окна</p>
       <my-button label="Подтвердить"></my-button>
     </my-modal>`
  • События и коллбэки:

Компоненты Haunted могут отправлять события наружу через стандартный механизм CustomEvent:

const handleClick = () => {
  const event = new CustomEvent('confirmed', { detail: { value: true } });
  this.dispatchEvent(event);
};
  • Контекстное состояние через кастомные хуки, которые создаются с useState или useReducer на верхнем уровне и передаются вниз через свойства.

Инкапсуляция стилей

Использование Shadow DOM позволяет изолировать CSS каждого компонента:

customElements.define('styled-button', component(MyButton, { shadowRootOptions: { mode: 'open' } }));

В этом случае стили компонента не будут конфликтовать с глобальными стилями страницы.

Переиспользуемые хуки UI-кита

Для больших UI-китов удобно создавать собственные хуки:

import { useState, useEffect } from 'haunted';

export function useToggle(initial = false) {
  const [state, setState] = useState(initial);
  const toggle = () => setState(!state);
  return [state, toggle];
}

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

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

Haunted отлично работает с ES-модулями и современными сборщиками вроде Vite или Webpack. Компоненты можно импортировать напрямую:

import './ui-kit/button/button.js';
import './ui-kit/modal/modal.js';

Современные сборщики позволяют автоматически обрабатывать CSS и оптимизировать рендеринг для больших UI-китов.

Оптимизация и производительность

Haunted минимально перегружает DOM и обеспечивает быстрый ререндер только изменённых частей благодаря виртуальному DOM, встроенному в библиотеку. Для больших наборов компонентов рекомендуется:

  • Разделять компонент на мелкие подкомпоненты.
  • Использовать мемоизацию функций через useCallback или кастомные хуки.
  • Минимизировать лишние эффекты через массив зависимостей в useEffect.