Перейти к содержимому

Redux Toolkit — введение

Классический Redux требовал огромного количества boilerplate: action types, action creators, reducers, spread операторы. Redux Toolkit (RTK) решает эти проблемы:

ПроблемаКлассический ReduxRedux Toolkit
Action typesСтроки вручнуюАвтогенерация из slice
Action creatorsВручнуюАвтогенерация
Immutable updates{ ...state, field }Immer (мутация)
Async логикаredux-thunk вручнуюcreateAsyncThunk
Конфигурация storeмного настроекconfigureStore
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() } };
},
},
},
});
// Экспортируем actions
export const { increment, decrement, setStep, reset, addByAmount } = counterSlice.actions;
// Экспортируем reducer
export default counterSlice.reducer;
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',
});
// Типы для TypeScript
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export default store;

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 ===
// 1. Action types
const INCREMENT = 'counter/INCREMENT';
const DECREMENT = 'counter/DECREMENT';
const SET_VALUE = 'counter/SET_VALUE';
// 2. Action creators
const increment = () => ({ type: INCREMENT });
const decrement = () => ({ type: DECREMENT });
const setValue = (value: number) => ({ type: SET_VALUE, payload: value });
// 3. Reducer
function 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;