Redux: управляемое состояние в React-приложениях
Redux собирает состояние React-приложения в единый store, упрощая отладку, тестирование и работу с асинхронными запросами.
Когда список товаров “лагает”, а кнопка “Добавить в корзину” не меняет количество, значит, состояние рассредоточено. Redux собирает его в одно место, делая данные предсказуемыми и удобными для тестов.
Что такое Redux и зачем он нужен?
Redux библиотека для управления состоянием в JavaScript-приложениях. Она предоставляет единый источник истины store, где хранится все UI-состояние. Вместо разбросанных useState
по сотням компонентов вы держите данные в одном месте и меняете их через actions и reducers. Это упрощает отладку и позволяет откатывать изменения (time-travel debugging).
Ключевые понятия Redux
Элемент | Описание |
---|---|
Store | Хранилище, в котором лежит все состояние приложения |
Action | Объект { type, payload } , описывающий событие |
Reducer | Функция (state, action) => newState принимает состояние и действие, возвращает новое |
Middleware | Плазма между dispatch и reducer . Позволяет работать с асинхронностью, логированием и т.п. |
Эти четыре концепции образуют цикл данных: UI → dispatch(action)
→ middleware → reducer → новый state → UI.
Минимальный пример store в React
// store.js
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk"; // middleware для async-логики
// Начальное состояние
const initialState = {
cart: [],
};
// Reducer
function cartReducer(state = initialState, action) {
switch (action.type) {
case "ADD_ITEM":
return { ...state, cart: [...state.cart, action.payload] };
case "REMOVE_ITEM":
return {
...state,
cart: state.cart.filter((i) => i.id !== action.payload.id),
};
default:
return state;
}
}
export const store = createStore(cartReducer, applyMiddleware(thunk));
// component.jsx
import React from "react";
import { useSelector, useDispatch } from "react-redux";
export default function Cart() {
const cart = useSelector((state) => state.cart);
const dispatch = useDispatch();
const addItem = (item) => dispatch({ type: "ADD_ITEM", payload: item });
return (
<div>
<h2>Корзина</h2>
<ul>
{cart.map((i) => (
<li key={i.id}>{i.name}</li>
))}
</ul>
<button onClick={() => addItem({ id: Date.now(), name: "Товар" })}>
Добавить
</button>
</div>
);
}
Мы создали store
, подключили thunk
-middleware и получили рабочую корзину. Чтобы снизить количество ререндеров, посмотрите на мемоизацию компонентов.
Как добавлять асинхронную логику в Redux
Самая частая ошибка новичков выполнять запрос внутри reducer
. Это нарушает чистоту функций и приводит к непредсказуемым багам. Перенесите запрос в middleware. Самый популярный вариант redux-thunk
, который позволяет писать асинхронные action-creators:
// actions.js
export const fetchProducts = () => async (dispatch) => {
dispatch({ type: "PRODUCTS_PENDING" });
try {
const res = await fetch("/api/products");
const data = await res.json();
dispatch({ type: "PRODUCTS_FULFILLED", payload: data });
} catch (e) {
dispatch({ type: "PRODUCTS_REJECTED", error: e });
}
};
Такой подход делает состояние предсказуемым и упрощает тестирование. Для более строгого контроля побочных эффектов обратите внимание на Redux-Saga.
Когда (и когда не) использовать Redux
Сценарий | Рекомендация |
---|---|
Простой UI-компонент с несколькими локальными стейтами | Достаточно useState /useReducer |
Большое приложение с множеством взаимосвязанных данных | Redux ваш главный помощник |
Требуется сервер-сайд рендеринг (SSR) | Подключайте configureStore из @reduxjs/toolkit |
Нужно глобальное кэширование запросов | Сочетайте Redux с виртуализацией списка и React.memo |
Если вы решили использовать Redux, начните с @reduxjs/toolkit
. Он генерирует типовые action-creators и сокращает бойлерплейт, позволяя сосредоточиться на бизнес-логике.
FAQ / Чеклист
- Не мутируйте state в reducer. Используйте спред-операторы или
immer
. - Не храните функции в Redux-state они не сериализуются и ломают DevTools.
- Подключайте только нужные slice-ы через
useSelector
. Иначе каждый ререндер будет проверять все состояние. - Проверяйте типы action-ов (TypeScript или
prop-types
). Это защищает от опечаток. - Удаляйте подписки в кастомных хуках, если используете
store.subscribe
вручную.
Redux становится естественным выбором, когда приложение растет, а данные начинают пересекаться. Одна централизованная точка управления упрощает отладку, а DevTools позволяют восстановить любое состояние.