Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как работает useReducer в React
useReducer - это продвинутый способ управления состоянием, идеален для сложной логики с множеством действий. Это альтернатива useState для более сложных сценариев.
1. Базовая концепция
import { useReducer } from 'react';
// Reducer - чистая функция, которая определяет как меняется state
const reducer = (state, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
case 'RESET':
return { count: 0 };
default:
return state;
}
};
// В компоненте
const Counter = () => {
const [state, dispatch] = useReducer(
reducer,
{ count: 0 } // initial state
);
return (
<div>
<p>Счёт: {state.count}</p>
<button onClick={() => dispatch({ type: 'INCREMENT' })}>
+
</button>
<button onClick={() => dispatch({ type: 'DECREMENT' })}>
-
</button>
<button onClick={() => dispatch({ type: 'RESET' })}>
Сброс
</button>
</div>
);
};
2. Структура и синтаксис
// useReducer(reducer, initialState, init?)
const [state, dispatch] = useReducer(reducer, initialValue, init);
// reducer - функция (state, action) => newState
// initialState - начальное значение
// init - опциональная функция инициализации
// action обычно имеет вид:
const action = {
type: 'ACTION_NAME', // обязательно
payload: { data } // опциональные данные
};
3. Пример с payload
const initialState = {
todos: [],
filter: 'all',
loading: false
};
const todoReducer = (state, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
...state,
todos: [
...state.todos,
{ id: Date.now(), text: action.payload, done: false }
]
};
case 'REMOVE_TODO':
return {
...state,
todos: state.todos.filter(t => t.id !== action.payload)
};
case 'TOGGLE_TODO':
return {
...state,
todos: state.todos.map(t =>
t.id === action.payload ? { ...t, done: !t.done } : t
)
};
case 'SET_FILTER':
return { ...state, filter: action.payload };
case 'SET_LOADING':
return { ...state, loading: action.payload };
default:
return state;
}
};
const TodoApp = () => {
const [state, dispatch] = useReducer(todoReducer, initialState);
const handleAddTodo = (text) => {
dispatch({ type: 'ADD_TODO', payload: text });
};
const handleRemove = (id) => {
dispatch({ type: 'REMOVE_TODO', payload: id });
};
const handleToggle = (id) => {
dispatch({ type: 'TOGGLE_TODO', payload: id });
};
return (
<div>
<input
onKeyPress={(e) => {
if (e.key === 'Enter') {
handleAddTodo(e.target.value);
e.target.value = '';
}
}}
placeholder='Новая задача'
/>
{state.todos.map(todo => (
<div key={todo.id}>
<input
type='checkbox'
checked={todo.done}
onChange={() => handleToggle(todo.id)}
/>
<span>{todo.text}</span>
<button onClick={() => handleRemove(todo.id)}>Удалить</button>
</div>
))}
</div>
);
};
4. Функция инициализации (init)
Опциональный третий параметр для ленивой инициализации:
const init = (initialCount) => {
// Сложная логика инициализации
return {
count: initialCount,
timestamp: Date.now()
};
};
const [state, dispatch] = useReducer(
reducer,
0, // initialArg
init // функция инициализации
);
// init вызовется только один раз при монтировании
5. Константы для типов действий
Хороший стиль - использовать константы:
// actions.ts
export const TODO_ACTIONS = {
ADD: 'TODO_ADD',
REMOVE: 'TODO_REMOVE',
TOGGLE: 'TODO_TOGGLE',
SET_FILTER: 'TODO_SET_FILTER'
};
// reducer.ts
const todoReducer = (state, action) => {
switch (action.type) {
case TODO_ACTIONS.ADD:
// ...
case TODO_ACTIONS.REMOVE:
// ...
default:
return state;
}
};
// component.ts
dispatch({ type: TODO_ACTIONS.ADD, payload: text });
6. Асинхронные операции с useReducer
const initialState = {
data: null,
loading: false,
error: null
};
const dataReducer = (state, action) => {
switch (action.type) {
case 'LOADING':
return { ...state, loading: true, error: null };
case 'SUCCESS':
return { data: action.payload, loading: false, error: null };
case 'ERROR':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
};
const DataFetcher = () => {
const [state, dispatch] = useReducer(dataReducer, initialState);
const fetchData = async (url) => {
dispatch({ type: 'LOADING' });
try {
const response = await fetch(url);
const data = await response.json();
dispatch({ type: 'SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'ERROR', payload: error.message });
}
};
if (state.loading) return <div>Загрузка...</div>;
if (state.error) return <div>Ошибка: {state.error}</div>;
return <div>{JSON.stringify(state.data)}</div>;
};
7. useReducer vs useState
// ❌ useState для сложной логики - становится запутанным
const Complex = () => {
const [count, setCount] = useState(0);
const [history, setHistory] = useState([]);
const [canUndo, setCanUndo] = useState(false);
const increment = () => {
setHistory([...history, count]);
setCount(count + 1);
setCanUndo(history.length > 0);
};
// Логика разбросана, сложно отследить
};
// ✅ useReducer для сложной логики - ясно и организовано
const Complex = () => {
const [state, dispatch] = useReducer(complexReducer, initialState);
const increment = () => {
dispatch({ type: 'INCREMENT' });
};
// Вся логика в одном месте (reducer)
};
8. Когда использовать useReducer
// Используй useReducer если:
// 1. Множество связанных state переменных
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [phone, setPhone] = useState('');
const [address, setAddress] = useState('');
// -> Лучше одно состояние в reducer
// 2. Сложная логика обновления
const handleSubmit = () => {
validate();
transform();
submit();
reset();
};
// -> Каждый шаг - отдельный action в reducer
// 3. Нужна история состояний
const [history, setHistory] = useState([]);
const undo = () => {
setHistory(history.slice(0, -1));
setState(history[history.length - 2]);
};
// -> Reducer может хранить историю
// 4. Передача dispatch в контекст
const [state, dispatch] = useReducer(reducer, initial);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
// Легко передавать одну функцию вместо множества setState
9. useReducer с Context для глобального состояния
const AppContext = createContext();
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(appReducer, initialState);
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
};
const useAppContext = () => {
const context = useContext(AppContext);
if (!context) {
throw new Error('useAppContext должен быть внутри AppProvider');
}
return context;
};
// В компоненте
const ChildComponent = () => {
const { state, dispatch } = useAppContext();
return (
<button onClick={() => dispatch({ type: 'INCREMENT' })}>
{state.count}
</button>
);
};
10. Тестирование reducer
import { describe, it, expect } from 'vitest';
import { todoReducer } from './reducer';
describe('todoReducer', () => {
it('добавляет новую задачу', () => {
const state = { todos: [] };
const action = { type: 'ADD_TODO', payload: 'Купить молоко' };
const newState = todoReducer(state, action);
expect(newState.todos).toHaveLength(1);
expect(newState.todos[0].text).toBe('Купить молоко');
});
it('удаляет задачу по id', () => {
const state = {
todos: [{ id: 1, text: 'Task' }]
};
const action = { type: 'REMOVE_TODO', payload: 1 };
const newState = todoReducer(state, action);
expect(newState.todos).toHaveLength(0);
});
});
Ключевые преимущества useReducer
- Предсказуемость - все изменения state проходят через reducer
- Тестируемость - reducer это чистая функция, легко тестировать
- Масштабируемость - удобно когда состояние усложняется
- Отладка - видна история всех actions
- Переиспользование - одного reducer для нескольких компонентов
Вывод: useReducer это мощный инструмент для управления сложным состоянием, вторая опция после useState.