Haunted — это библиотека для создания веб-компонентов на основе Web Components с использованием реактивного подхода, схожего с React Hooks. Центральное место в Haunted занимает управление состоянием компонентов и реактивное обновление интерфейса.
useStateuseState позволяет хранить локальное состояние
компонента. Основной принцип работы — при изменении состояния компонент
автоматически перерисовывается.
import { html, component, 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));
Особенности использования:
setCount компонент перерисовывается.setItems([...items, newItem]);
useEffectuseEffect позволяет реагировать на изменения состояния
или жизненный цикл компонента.
import { useEffect } from 'haunted';
useEffect(() => {
console.log('Компонент смонтирован');
return () => console.log('Компонент размонтирован');
}, []);
useEffect(() => {
console.log(`Счётчик изменился: ${count}`);
}, [count]);
useReducerДля сложной логики состояния лучше использовать
useReducer. Он позволяет управлять состоянием через
редьюсер — функцию, которая принимает текущее состояние
и действие, возвращая новое состояние.
import { useReducer } from 'haunted';
function reducer(state, action) {
switch(action.type) {
case 'increment':
return { ...state, count: state.count + 1 };
case 'decrement':
return { ...state, 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>
`;
}
Преимущества useReducer:
Haunted поддерживает композицию хуков, что позволяет создавать сложные состояния из простых.
function useCounter(initial = 0) {
const [count, setCount] = useState(initial);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return { count, increment, decrement };
}
function CounterComponent() {
const counter = useCounter(5);
return html`
<div>
<p>Счётчик: ${counter.count}</p>
<button @click=${counter.increment}>+</button>
<button @click=${counter.decrement}>-</button>
</div>
`;
}
Такой подход повышает читаемость и позволяет переиспользовать логику между компонентами.
Для массивов и объектов важно помнить о неизменяемости состояния.
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = todo => setTodos([...todos, todo]);
const removeTodo = index => setTodos(todos.filter((_, i) => i !== index));
return html`
<ul>
${todos.map((todo, i) => html`<li>${todo} <button @click=${() => removeTodo(i)}>Удалить</button></li>`)}
</ul>
<button @click=${() => addTodo('Новое дело')}>Добавить</button>
`;
}
todos.push(...)) не вызовет
обновление интерфейса.Haunted позволяет работать с асинхронными операциями. Чаще всего
используется useEffect с асинхронными функциями:
useEffect(() => {
let canceled = false;
async function fetchData() {
const response = await fetch('https://jsonplaceholder.typicode.com/todos/1');
const data = await response.json();
if (!canceled) setData(data);
}
fetchData();
return () => { canceled = true };
}, []);
canceled предотвращает обновление состояния
после размонтирования компонента.useCounter,
useTodos).Состояние можно синхронизировать с атрибутами веб-компонента через
хуки useState или useReducer, обрабатывая их в
attributeChangedCallback.
function MyComponent({ initial }) {
const [value, setValue] = useState(initial);
return html`<p>Значение: ${value}</p>`;
}
useEffect для побочных эффектов,
асинхронных операций и синхронизации.Haunted обеспечивает гибкую, но строгую модель управления состоянием, позволяя строить реактивные компоненты с любыми уровнями сложности.