useReducer: сложное состояние
useReducer — альтернатива useState для сложного состояния. Логика обновления выносится в отдельную функцию-редьюсер.
Синтаксис
const [state, dispatch] = useReducer(reducer, initialState);
state— текущее состояниеdispatch— функция для отправки действийreducer— функция, которая обрабатывает действия и возвращает новое состояние
Редьюсер
type State = {
count: number;
};
type Action =
| { type: 'increment' }
| { type: 'decrement' }
| { type: 'reset' }
| { type: 'set'; payload: number };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
case 'set':
return { count: action.payload };
default:
return state;
}
}
Использование
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+1</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-1</button>
<button onClick={() => dispatch({ type: 'reset' })}>Сброс</button>
<button onClick={() => dispatch({ type: 'set', payload: 10 })}>10</button>
</>
);
}
Почему лучше useState
Когда состояний много и они меняются вместе, useState превращается в кашу. Редьюсер даёт:
- Вся логика обновления в одном месте
- Действия явно названы
- Легко добавить новые действия
- Легко тестировать
Пример: форма
type State = {
name: string;
email: string;
errors: Record<string, string>;
isSubmitting: boolean;
};
type Action =
| { type: 'setField'; field: string; value: string }
| { type: 'setErrors'; errors: Record<string, string> }
| { type: 'submit' }
| { type: 'reset' };
function reducer(state: State, action: Action): State {
switch (action.type) {
case 'setField':
return { ...state, [action.field]: action.value };
case 'setErrors':
return { ...state, errors: action.errors, isSubmitting: false };
case 'submit':
return { ...state, isSubmitting: true };
case 'reset':
return { name: '', email: '', errors: {}, isSubmitting: false };
}
}
Ленивая инициализация
const [state, dispatch] = useReducer(reducer, initialArg, init);
init — функция, которая получает initialArg и возвращает начальное состояние.
function init(initialCount: number) {
return { count: initialCount };
}
useReducer(reducer, props.initialCount, init);
Когда использовать
- Много связанных состояний
- Следующее состояние зависит от предыдущего сложным образом
- Логика обновления повторяется
- Нужно тестировать логику отдельно
useState vs useReducer
- useState — для простых значений
- useReducer — для сложных структур и переходов
Если обновлений больше 3–4 и они взаимосвязаны — useReducer чище.
Итоги
useReducer— для сложного состояния- Логика в редьюсере
- Действия через
dispatch - Легко тестировать и расширять
- Альтернатива
useState, не замена