Haunted — это библиотека для создания веб-компонентов на основе стандартов Web Components с реактивностью, аналогичной React Hooks. Основное отличие заключается в том, что Haunted не использует виртуальный DOM. Вместо этого она напрямую взаимодействует с настоящим DOM, что упрощает производительность и уменьшает накладные расходы.
Ключевые моменты, на которые следует обратить внимание при миграции с React:
html
из haunted.useState, useEffect, useRef,
useReducer, но применяются в контексте функции
веб-компонента.В React:
function Counter() {
const [count, setCount] = React.useState(0);
return (
<div>
<p>{count}</p>
<button onCl ick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
В Haunted:
import { html, component, useState } from 'haunted';
function counter() {
const [count, setCount] = useState(0);
return html`
<div>
<p>${count}</p>
<button @click=${() => setCount(count + 1)}>Increment</button>
</div>
`;
}
customElements.define('my-counter', component(counter));
Особенности:
@ вместо on,
например @click=${handler}.component() оборачивает функцию и превращает её в
полноценный веб-компонент.html.Haunted поддерживает несколько видов состояний и их обновлений:
useState(initialValue) — базовое состояние, аналог
React.useReducer(reducer, initialState) — для сложной логики
управления состоянием.useRef(initialValue) — хранение изменяемых данных без
триггера обновлений DOM.Пример использования useReducer:
import { html, component, useReducer } from 'haunted';
function reducer(state, action) {
switch (action.type) {
case 'increment':
return state + 1;
case 'decrement':
return state - 1;
default:
return state;
}
}
function counter() {
const [count, dispatch] = useReducer(reducer, 0);
return html`
<div>
<p>${count}</p>
<button @click=${() => dispatch({ type: 'increment' })}>+</button>
<button @click=${() => dispatch({ type: 'decrement' })}>-</button>
</div>
`;
}
customElements.define('my-counter', component(counter));
Haunted предоставляет хук useEffect, аналогичный React.
Его особенности:
Пример:
import { html, component, useState, useEffect } from 'haunted';
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const id = setInterval(() => setSeconds(s => s + 1), 1000);
return () => clearInterval(id);
}, []);
return html`<p>Time: ${seconds}s</p>`;
}
customElements.define('my-timer', component(Timer));
Отличие от React: Haunted не имеет дополнительных
методов жизненного цикла, кроме useEffect. Все логики,
связанные с монтированием, обновлением и размонтированием, управляются
через хуки.
В Haunted props передаются как обычные атрибуты веб-компонентов. Они
доступны в функции компонента через аргумент props.
Пример:
function Greeting({ name }) {
return html`<p>Hello, ${name}!</p>`;
}
customElements.define('my-greeting', component(Greeting));
При использовании:
<my-greeting name="Alice"></my-greeting>
Особенности:
const el = document.createElement('my-greeting');
el.name = 'Bob';
document.body.appendChild(el);
Haunted не имеет встроенного контекста, как React. Для передачи состояния между компонентами можно использовать:
CustomEvent) и
слушатели.useState или
сторонние состояния, например, valtio или
zustand.При переносе React-компонентов в Haunted следует учитывать:
onClick →
@click, onChange → @change и
т.д.useEffect выполняется в контексте веб-компонента.useRef работает только для
хранения данных, привязка к DOM через ref.current
невозможна, используйте стандартные querySelector в Shadow
DOM.Haunted по умолчанию использует Shadow DOM, что изолирует стили. Для миграции из React:
:host или
вставлять <style> внутри компонента.В Haunted списки рендерятся с помощью map внутри
шаблонного литерала:
function ItemList({ items }) {
return html`
<ul>
${items.map(item => html`<li>${item}</li>`)}
</ul>
`;
}
customElements.define('item-list', component(ItemList));
Условный рендеринг выполняется через тернарные операторы:
const loggedIn = true;
return html`
${loggedIn ? html`<p>Welcome back!</p>` : html`<p>Please login</p>`}
`;
Асинхронные действия, такие как запросы к API, выполняются в
useEffect. В Haunted можно использовать async-функции, но
внутри эффекта их нужно вызывать корректно:
useEffect(() => {
let cancelled = false;
async function fetchData() {
const response = await fetch('/api/data');
const data = await response.json();
if (!cancelled) setData(data);
}
fetchData();
return () => { cancelled = true; };
}, []);
Такой подход предотвращает обновление состояния после размонтирования компонента.
map.Haunted позволяет плавно перенести большинство концепций React, сохранив знакомую работу с хуками, состоянием и эффектами, но при этом использовать преимущества нативных веб-компонентов и Shadow DOM для более компактного и быстрого кода.