Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
useReducer: практическое применение в React
useReducer — это хук React для управления сложным состоянием приложения. Он особенно полезен, когда состояние зависит от предыдущих значений или когда нужно управлять несколькими связанными полями состояния. Расскажу о реальных сценариях использования.
Когда использовать useReducer
Сложное состояние с множественными обновлениями Когда у вас есть несколько связанных значений состояния, которые обновляются вместе.
Логика переходов состояния Когда состояние имеет несколько дискретных состояний (loading, success, error).
Оптимизация производительности useReducer удобнее при передаче dispatch функции глубоко вложенным компонентам (вместо множества callback функций).
Пример 1: Форма с валидацией
import { useReducer } from 'react';
const initialState = {
name: '',
email: '',
password: '',
errors: {},
isSubmitting: false
};
function formReducer(state, action) {
switch (action.type) {
case 'FIELD_CHANGE':
return {
...state,
[action.field]: action.value,
errors: {
...state.errors,
[action.field]: null
}
};
case 'SET_ERRORS':
return { ...state, errors: action.errors };
case 'SUBMIT_START':
return { ...state, isSubmitting: true };
case 'SUBMIT_SUCCESS':
return initialState;
case 'SUBMIT_ERROR':
return { ...state, isSubmitting: false, errors: action.errors };
default:
return state;
}
}
function RegistrationForm() {
const [state, dispatch] = useReducer(formReducer, initialState);
const validateForm = () => {
const errors = {};
if (!state.name) errors.name = 'Имя обязательно';
if (!state.email) errors.email = 'Email обязателен';
if (state.password.length < 8) errors.password = 'Минимум 8 символов';
return errors;
};
const handleChange = (e) => {
const { name, value } = e.target;
dispatch({
type: 'FIELD_CHANGE',
field: name,
value: value
});
};
const handleSubmit = async (e) => {
e.preventDefault();
const errors = validateForm();
if (Object.keys(errors).length > 0) {
dispatch({ type: 'SET_ERRORS', errors });
return;
}
dispatch({ type: 'SUBMIT_START' });
try {
const response = await fetch('/api/register', {
method: 'POST',
body: JSON.stringify(state)
});
if (response.ok) {
dispatch({ type: 'SUBMIT_SUCCESS' });
} else {
dispatch({
type: 'SUBMIT_ERROR',
errors: { form: 'Ошибка регистрации' }
});
}
} catch (error) {
dispatch({
type: 'SUBMIT_ERROR',
errors: { form: error.message }
});
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<input
type="text"
name="name"
value={state.name}
onChange={handleChange}
placeholder="Имя"
/>
{state.errors.name && <span className="error">{state.errors.name}</span>}
</div>
<div>
<input
type="email"
name="email"
value={state.email}
onChange={handleChange}
placeholder="Email"
/>
{state.errors.email && <span className="error">{state.errors.email}</span>}
</div>
<button type="submit" disabled={state.isSubmitting}>
{state.isSubmitting ? 'Регистрация...' : 'Зарегистрироваться'}
</button>
</form>
);
}
Пример 2: Асинхронные операции
const initialState = {
data: null,
loading: false,
error: null,
retryCount: 0
};
function dataReducer(state, action) {
switch (action.type) {
case 'FETCH_START':
return { ...state, loading: true, error: null };
case 'FETCH_SUCCESS':
return {
...state,
loading: false,
data: action.payload,
retryCount: 0
};
case 'FETCH_ERROR':
return {
...state,
loading: false,
error: action.error,
retryCount: state.retryCount + 1
};
default:
return state;
}
}
function DataFetcher() {
const [state, dispatch] = useReducer(dataReducer, initialState);
const fetchData = async () => {
dispatch({ type: 'FETCH_START' });
try {
const response = await fetch('/api/data');
const data = await response.json();
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (error) {
dispatch({ type: 'FETCH_ERROR', error: error.message });
}
};
return (
<div>
<button onClick={fetchData}>Загрузить данные</button>
{state.loading && <p>Загрузка...</p>}
{state.error && <p>Ошибка: {state.error}</p>}
{state.data && <pre>{JSON.stringify(state.data, null, 2)}</pre>}
</div>
);
}
Пример 3: Состояние фильтров
const initialState = {
searchQuery: '',
filters: {
category: 'all',
priceRange: [0, 1000],
sortBy: 'popularity'
},
results: [],
hasSearched: false
};
function searchReducer(state, action) {
switch (action.type) {
case 'SEARCH_QUERY_CHANGE':
return { ...state, searchQuery: action.value };
case 'FILTER_CHANGE':
return {
...state,
filters: { ...state.filters, [action.field]: action.value }
};
case 'SEARCH_EXECUTE':
return { ...state, results: action.results, hasSearched: true };
case 'RESET_FILTERS':
return { ...initialState, hasSearched: true };
default:
return state;
}
}
function SearchComponent() {
const [state, dispatch] = useReducer(searchReducer, initialState);
const handleSearch = async () => {
const query = new URLSearchParams({
q: state.searchQuery,
category: state.filters.category,
minPrice: state.filters.priceRange[0],
maxPrice: state.filters.priceRange[1],
sort: state.filters.sortBy
});
const response = await fetch(`/api/search?${query}`);
const results = await response.json();
dispatch({ type: 'SEARCH_EXECUTE', results });
};
return (
<div>
<input
type="text"
value={state.searchQuery}
onChange={(e) => dispatch({
type: 'SEARCH_QUERY_CHANGE',
value: e.target.value
})}
placeholder="Поиск..."
/>
<button onClick={handleSearch}>Поиск</button>
<button onClick={() => dispatch({ type: 'RESET_FILTERS' })}>
Очистить
</button>
{state.hasSearched && (
<div>
<p>Найдено: {state.results.length}</p>
</div>
)}
</div>
);
}
Заключение
useReducer идеален для управления сложным состоянием с предсказуемой логикой переходов. Он делает код более организованным, тестируемым и масштабируемым. В производственных приложениях я использовал его для форм, фильтров, асинхронных операций и управления UI состоянием.