Haunted — это библиотека для создания веб-компонентов на основе функционального подхода с использованием хуков, вдохновленных React. Одним из ключевых элементов архитектуры Haunted является редьюсер (reducer), который позволяет управлять состоянием компонента в декларативном стиле, обеспечивая предсказуемое поведение приложения.
Редьюсер в Haunted — это функция, принимающая текущее состояние и действие (action), и возвращающая новое состояние. Он реализует принцип чистой функции: не изменяет исходные данные, а всегда возвращает новый объект состояния.
Сигнатура редьюсера выглядит так:
function reducer(state, action) {
// Возвращает новое состояние
}
state — текущие данные состояния компонента.action — объект, описывающий событие или команду для
изменения состояния. Обычно имеет структуру
{ type: 'ACTION_TYPE', payload: any }.Пример базового редьюсера:
function counterReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
}
useReducerHaunted предоставляет хук useReducer, который
интегрирует редьюсер в функциональный компонент:
import { component, useReducer, html } from 'haunted';
function Counter() {
const [state, dispatch] = useReducer(counterReducer, { count: 0 });
return html`
<div>
<p>Счётчик: ${state.count}</p>
<button @click=${() => dispatch({ type: 'INCREMENT' })}>+</button>
<button @click=${() => dispatch({ type: 'DECREMENT' })}>-</button>
</div>
`;
}
customElements.define('x-counter', component(Counter));
useReducer(reducer, initialState) возвращает массив
[state, dispatch]:
state — текущее состояние.dispatch — функция, которая принимает действие и
вызывает редьюсер для обновления состояния.Ключевой момент: каждое изменение состояния через
dispatch вызывает повторный рендер компонента с новым
состоянием.
Actions — это объекты, которые описывают какое изменение должно произойти.
Пример расширенного использования:
function todoReducer(state, action) {
switch (action.type) {
case 'ADD_TODO':
return [...state, { text: action.payload, completed: false }];
case 'TOGGLE_TODO':
return state.map((todo, idx) =>
idx === action.payload ? { ...todo, completed: !todo.completed } : todo
);
case 'REMOVE_TODO':
return state.filter((_, idx) => idx !== action.payload);
default:
return state;
}
}
payload содержит данные, необходимые для выполнения
действия.combineReducers (аналогично Redux).Для сложных компонентов часто удобно разделять управление состоянием на отдельные логические блоки:
function todosReducer(state, action) { /* как выше */ }
function filterReducer(state, action) {
switch (action.type) {
case 'SET_FILTER':
return action.payload;
default:
return state;
}
}
function rootReducer(state, action) {
return {
todos: todosReducer(state.todos, action),
filter: filterReducer(state.filter, action)
};
}
const initialState = { todos: [], filter: 'all' };
Такой подход делает состояние структурированным, а логику — легко тестируемой.
Редьюсеры особенно полезны, когда:
Пример компонента с несколькими редьюсерами:
function App() {
const [state, dispatch] = useReducer(rootReducer, initialState);
return html`
<div>
<x-todo-list
.todos=${state.todos}
.filter=${state.filter}
@add=${e => dispatch({ type: 'ADD_TODO', payload: e.detail })}
@toggle=${e => dispatch({ type: 'TOGGLE_TODO', payload: e.detail })}
></x-todo-list>
<x-filter
.currentFilter=${state.filter}
@change=${e => dispatch({ type: 'SET_FILTER', payload: e.detail })}
></x-filter>
</div>
`;
}
type) и содержать минимально необходимые данные
(payload).Редьюсеры в Haunted обеспечивают чистую, модульную и предсказуемую архитектуру компонентов, делая управление состоянием удобным и прозрачным даже в сложных приложениях.