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 поддерживает несколько встроенных хуков, аналогичных React, что делает управление состоянием и эффектами гибким и лаконичным.
import { useState } from 'haunted';
function Counter() {
const [count, setCount] = useState(0);
return html`
<div>
<span>${count}</span>
<button @click=${() => setCount(count + 1)}>Увеличить</button>
</div>
`;
}
import { useEffect } from 'haunted';
useEffect(() => {
console.log('Компонент был обновлен или создан');
}, [count]);
import { useRef, useEffect } from 'haunted';
function InputFocus() {
const inputRef = useRef();
useEffect(() => {
inputRef.current.focus();
}, []);
return html`<input ref=${inputRef} />`;
}
UI-кит на Haunted строится вокруг переиспользуемых компонентов с управляемым состоянием и чистым API. Основные принципы организации:
Пример базовой структуры:
ui-kit/
├─ button/
│ ├─ button.js
│ └─ button.css
├─ modal/
│ ├─ modal.js
│ └─ modal.css
└─ index.js
Компонент модального окна с управлением видимостью через состояние:
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-китов удобно создавать собственные хуки:
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.