Haunted — это легковесная библиотека для создания веб-компонентов с использованием реактивного подхода, вдохновлённого React Hooks. Она построена поверх стандартного API Web Components и предоставляет возможность управлять состоянием и эффектами с минимальным синтаксисом. Основное преимущество Haunted заключается в том, что она не требует виртуального DOM и работает напрямую с настоящим DOM, что улучшает производительность при небольших и средних проектах.
В Haunted компонент создаётся как функция. Основная функция
библиотеки — component, которая превращает функцию в
веб-компонент:
import { html, component } from 'haunted';
function MyCounter({ initial = 0 }) {
const [count, setCount] = useState(initial);
return html`
<div>
<p>Счётчик: ${count}</p>
<button @click=${() => setCount(count + 1)}>Увеличить</button>
</div>
`;
}
customElements.define('my-counter', component(MyCounter));
Ключевые моменты:
html используется для шаблонов, аналогично шаблонам
lit-html.useState управляет локальным состоянием
компонента.@click привязывает обработчик события к элементу.Haunted поддерживает большинство популярных хуков, аналогичных React:
[state, setState].useState, особенно полезна для сложной логики
состояния.Пример использования useEffect:
import { html, component, useState, useEffect } from 'haunted';
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('my-timer', component(Timer));
Важно: возвращаемая функция из useEffect используется
для очистки ресурсов при размонтировании компонента или при изменении
зависимостей.
Haunted позволяет работать с атрибутами и свойствами веб-компонента напрямую:
function Greeting({ name = 'Гость' }) {
return html`<p>Привет, ${name}!</p>`;
}
customElements.define('my-greeting', component(Greeting));
Атрибуты можно передавать при использовании:
<my-greeting name="Алексей"></my-greeting>
Haunted автоматически обновит компонент при изменении атрибутов.
Haunted поддерживает синтаксис привязки событий через
@event:
function Clicker() {
const [count, setCount] = useState(0);
return html`<button @click=${() => setCount(count + 1)}>
Нажато ${count} раз
</button>`;
}
customElements.define('my-clicker', component(Clicker));
Внутри обработчика доступны текущие значения состояния и любые функции компонента, что облегчает создание интерактивных интерфейсов.
Haunted можно легко интегрировать с другими библиотеками, такими как
lit-html для сложной разметки или fetch для
запросов к серверу. Пример запроса данных:
import { html, component, useState, useEffect } from 'haunted';
function UserList() {
const [users, setUsers] = useState([]);
useEffect(() => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(data => setUsers(data));
}, []);
return html`
<ul>
${users.map(user => html`<li>${user.name}</li>`)}
</ul>
`;
}
customElements.define('user-list', component(UserList));
Ключевой момент — реактивное обновление состояния
setUsers автоматически пересоздаёт шаблон без
дополнительного кода для рендера.
Haunted минимизирует количество обновлений DOM:
requestAnimationFrame или
debounce для оптимизации при интенсивных изменениях.Компоненты Haunted легко комбинируются и наследуются. Например:
function Card({ title, content }) {
return html`
<div class="card">
<h2>${title}</h2>
<p>${content}</p>
</div>
`;
}
function Dashboard() {
return html`
<card-component title="Привет" content="Это пример карточки"></card-component>
`;
}
customElements.define('card-component', component(Card));
customElements.define('dashboard-component', component(Dashboard));
Таким образом, создаётся иерархия компонентов, где каждый компонент остаётся независимым, а их взаимодействие строится через свойства и события.
Haunted позволяет хранить глобальное состояние через кастомные хуки:
import { useState, useEffect } from 'haunted';
const stateStore = (() => {
let state = 0;
const subscribers = new Set();
return {
get: () => state,
set: newState => {
state = newState;
subscribers.forEach(fn => fn(state));
},
subscribe: fn => subscribers.add(fn),
unsubscribe: fn => subscribers.delete(fn)
};
})();
function useGlobalState() {
const [value, setValue] = useState(stateStore.get());
useEffect(() => {
const UPDATE = val => setValue(val);
stateStore.subscribe(update);
return () => stateStore.unsubscribe(update);
}, []);
return [value, stateStore.se t];
}
Этот подход позволяет синхронизировать несколько компонентов без внешних библиотек управления состоянием.
Haunted не навязывает систему стилей. Можно использовать стандартные
<style> внутри шаблона:
function StyledButton() {
return html`
<style>
button {
padding: 8px 16px;
background: #007acc;
color: white;
border: none;
border-radius: 4px;
}
</style>
<button>Нажми меня</button>
`;
}
customElements.define('styled-button', component(StyledButton));
Для глобальной стилизации можно использовать CSS Variables и Shadow DOM, чтобы компоненты оставались изолированными.
Haunted хорошо сочетается с fetch или любыми
AJAX-библиотеками. Асинхронные запросы удобно обрабатывать в
useEffect, а реактивное состояние автоматически обновляет
UI. Это делает Haunted отличным инструментом для создания интерактивных
приложений с динамическим контентом без тяжёлых фреймворков.