Haunted — это лёгкая библиотека для создания веб-компонентов на базе хуков, вдохновлённая React, но использующая стандартные возможности Web Components. Основной задачей библиотеки является управление состоянием компонента и его реактивное обновление. Рассмотрим подробно механизмы подъёма состояния и передачи данных между компонентами.
Состояние в Haunted создаётся с помощью хука useState.
Этот хук возвращает пару значений: текущее состояние и функцию для его
обновления.
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>
</div>
`;
}
customElements.define('my-counter', component(Counter));
Ключевые моменты:
useState(initialValue) создаёт локальное состояние
компонента.setCount(newValue) обновляет состояние и автоматически
перерисовывает компонент.Локальное состояние ограничено компонентом, в котором оно создано. Когда несколько компонентов должны разделять данные или реагировать на одни и те же события, локального состояния недостаточно. Например, родительский компонент должен знать значение счётчика, находящегося в дочернем компоненте. Решение — подъём состояния (lifting state up).
Подъём состояния заключается в перемещении состояния из дочернего компонента в родительский и передаче функции обновления вниз через свойства.
function Parent() {
const [count, setCount] = useState(0);
return html`
<div>
<p>Общий счётчик: ${count}</p>
<my-counter .count=${count} .setCount=${setCount}></my-counter>
</div>
`;
}
function Child({ count, setCount }) {
return html`
<div>
<p>Счётчик в дочернем: ${count}</p>
<button @click=${() => setCount(count + 1)}>Увеличить</button>
</div>
`;
}
customElements.define('my-counter', component(Child));
customElements.define('my-parent', component(Parent));
Особенности реализации:
count
и setCount).props).Haunted позволяет использовать несколько хуков состояния в одном компоненте:
function Form() {
const [name, setName] = useState('');
const [age, setAge] = useState('');
return html`
<input
placeholder="Имя"
.value=${name}
@input=${e => setName(e.target.value)}
/>
<input
type="number"
placeholder="Возраст"
.value=${age}
@input=${e => setAge(e.target.value)}
/>
<p>Имя: ${name}, Возраст: ${age}</p>
`;
}
customElements.define('my-form', component(Form));
Выводы:
useState создаёт отдельную реактивную
переменную.props.useReducerДля сложных состояний, когда необходимо управлять множественными
связанными данными, предпочтительнее использовать
useReducer вместо множества useState. Он
работает по принципу редьюсера Redux:
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`
<p>Счётчик: ${state.count}</p>
<button @click=${() => dispatch({ type: 'increment' })}>+</button>
<button @click=${() => dispatch({ type: 'decrement' })}>-</button>
`;
}
customElements.define('my-counter', component(Counter));
Преимущества useReducer:
reducer.Иногда вместо прямой передачи функции обновления используется кастомное событие, которое дочерний компонент испускает при изменении состояния. Родитель ловит событие и обновляет своё состояние:
function Child() {
const [value, setValue] = useState('');
const handleInput = e => {
setValue(e.target.value);
e.target.dispatchEvent(new CustomEvent('value-changed', {
detail: e.target.value,
bubbles: true
}));
};
return html`<input .value=${value} @input=${handleInput} />`;
}
function Parent() {
const [text, setText] = useState('');
const handleChange = e => setText(e.detail);
return html`
<p>Текст: ${text}</p>
<my-child @value-changed=${handleChange}></my-child>
`;
}
customElements.define('my-child', component(Child));
customElements.define('my-parent', component(Parent));
Особенности:
useReducer или кастомные события.Haunted обеспечивает мощный, но лёгкий механизм управления состоянием, который в сочетании с подъёмом состояния позволяет строить сложные и реактивные интерфейсы, сохраняя модульность и чистоту кода.