Redux Toolkit — введение
Зачем Redux Toolkit?
Заголовок раздела «Зачем Redux Toolkit?»Классический Redux требовал огромного количества boilerplate: action types, action creators, reducers, spread операторы. Redux Toolkit (RTK) решает эти проблемы:
| Проблема | Классический Redux | Redux Toolkit |
|---|---|---|
| Action types | Строки вручную | Автогенерация из slice |
| Action creators | Вручную | Автогенерация |
| Immutable updates | { ...state, field } | Immer (мутация) |
| Async логика | redux-thunk вручную | createAsyncThunk |
| Конфигурация store | много настроек | configureStore |
createSlice
Заголовок раздела «createSlice»import { createSlice, PayloadAction } from '@reduxjs/toolkit';
interface CounterState { value: number; step: number;}
const initialState: CounterState = { value: 0, step: 1,};
const counterSlice = createSlice({ name: 'counter', initialState, reducers: { // Immer позволяет "мутировать" state — RTK делает snapshot автоматически increment(state) { state.value += state.step; // выглядит как мутация, но безопасно! }, decrement(state) { state.value -= state.step; }, setStep(state, action: PayloadAction<number>) { state.step = action.payload; }, reset() { return initialState; // или вернуть новый объект — тоже работает }, // Кастомный prepare для дополнительной логики addByAmount: { reducer(state, action: PayloadAction<{ amount: number; timestamp: number }>) { state.value += action.payload.amount; }, prepare(amount: number) { return { payload: { amount, timestamp: Date.now() } }; }, }, },});
// Экспортируем actionsexport const { increment, decrement, setStep, reset, addByAmount } = counterSlice.actions;// Экспортируем reducerexport default counterSlice.reducer;configureStore
Заголовок раздела «configureStore»import { configureStore } from '@reduxjs/toolkit';import counterReducer from './counterSlice';import usersReducer from './usersSlice';import authReducer from './authSlice';
const store = configureStore({ reducer: { counter: counterReducer, users: usersReducer, auth: authReducer, }, // middleware: по умолчанию включает redux-thunk + serializability check // Можно добавить своё: middleware: getDefaultMiddleware => getDefaultMiddleware({ serializableCheck: { ignoredActions: ['persist/PERSIST'], // например для redux-persist }, }), devTools: process.env.NODE_ENV !== 'production',});
// Типы для TypeScriptexport type RootState = ReturnType<typeof store.getState>;export type AppDispatch = typeof store.dispatch;
export default store;Immer под капотом
Заголовок раздела «Immer под капотом»RTK использует Immer — позволяет писать “мутирующий” код, который внутри создаёт новый immutable объект:
import { produce } from 'immer';
// Без Immer: сложные spread вложенных объектовconst oldState = { user: { profile: { settings: { theme: 'light' } } }};
const newState = { ...oldState, user: { ...oldState.user, profile: { ...oldState.user.profile, settings: { ...oldState.user.profile.settings, theme: 'dark', } } }};
// С Immer (как в RTK createSlice):const newState2 = produce(oldState, draft => { draft.user.profile.settings.theme = 'dark'; // просто!});
// В createSlice reducers — Immer работает автоматическиconst settingsSlice = createSlice({ name: 'settings', initialState: { user: { profile: { settings: { theme: 'light' } } } }, reducers: { setTheme(state, action: PayloadAction<'light' | 'dark'>) { state.user.profile.settings.theme = action.payload; // ✅ работает }, },});Классический Redux vs RTK — сравнение
Заголовок раздела «Классический Redux vs RTK — сравнение»// === КЛАССИЧЕСКИЙ REDUX ===
// 1. Action typesconst INCREMENT = 'counter/INCREMENT';const DECREMENT = 'counter/DECREMENT';const SET_VALUE = 'counter/SET_VALUE';
// 2. Action creatorsconst increment = () => ({ type: INCREMENT });const decrement = () => ({ type: DECREMENT });const setValue = (value: number) => ({ type: SET_VALUE, payload: value });
// 3. Reducerfunction counterReducer(state = { value: 0 }, action: any) { switch (action.type) { case INCREMENT: return { ...state, value: state.value + 1 }; case DECREMENT: return { ...state, value: state.value - 1 }; case SET_VALUE: return { ...state, value: action.payload }; default: return state; }}
// === REDUX TOOLKIT (эквивалент) ===const counterSliceRTK = createSlice({ name: 'counter', initialState: { value: 0 }, reducers: { increment: state => { state.value++; }, decrement: state => { state.value--; }, setValue: (state, action: PayloadAction<number>) => { state.value = action.payload; }, },});// Всё — actions, reducer, types — в одном месте!Структура файлов
Заголовок раздела «Структура файлов»src/ store/ index.ts — configureStore, RootState, AppDispatch hooks.ts — типизированные useSelector/useDispatch features/ counter/ counterSlice.ts — createSlice Counter.tsx — компонент users/ usersSlice.ts usersApi.ts// store/hooks.ts — типизированные хукиimport { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';import type { RootState, AppDispatch } from './index';
export const useAppDispatch = () => useDispatch<AppDispatch>();export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;