← Назад к вопросам

Как использовал useReducer на практике?

1.8 Middle🔥 182 комментариев
#React

Комментарии (2)

🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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 состоянием.

Как использовал useReducer на практике? | PrepBro