Redux Toolkit
Стор, действия, редьюсеры, createSlice с Immer и Redux в Next.
Обновлено
Что такое Redux
Redux — модель глобального состояния со строгими правилами:
- Всё глобальное состояние хранится в одном сторе.
- Состояние не меняют напрямую — отправляют (dispatch) действие: объект, описывающий, что произошло (
{ type: 'cart/add', payload: item }). - Редьюсер — чистая функция
(state, action) => newState— вычисляет новое состояние.
UIdispatch(action)действие→reducer(state, action) => newStateновое состояние→storeселектор→UIперерисовываетсяПлюс: любое изменение проходит через одно место → предсказуемо, тестируемо, а DevTools показывают каждое действие и умеют «перематывать» историю.
Redux Toolkit (RTK) — официальный и актуальный способ писать на Redux. «Классический» Redux (со switch, константами типов, connect) считается устаревшим.
Части Redux Toolkit
| Часть | Что делает |
|---|---|
configureStore |
создаёт стор с DevTools и middleware в комплекте |
createSlice |
описывает кусок состояния + его редьюсеры; генерирует действия автоматически |
useSelector |
читает кусок стора (компонент перерисовывается, только когда этот кусок меняется) |
useDispatch |
отправляет действия |
createAsyncThunk |
асинхронная логика (сегодня редко — для данных есть RTK Query) |
| RTK Query | запросы + кэш для серверного состояния — следующий урок |
Слайс
// entities/cart/model/cart-slice.ts
import { createSlice, type PayloadAction } from '@reduxjs/toolkit'
type CartItem = { id: number; qty: number }
const cartSlice = createSlice({
name: 'cart',
initialState: [] as CartItem[],
reducers: {
added(state, action: PayloadAction<number>) {
const item = state.find(i => i.id === action.payload)
if (item) item.qty++ // разрешённая «мутация» — см. Immer
else state.push({ id: action.payload, qty: 1 })
},
removed(state, action: PayloadAction<number>) {
return state.filter(i => i.id !== action.payload)
},
},
})
export const { added, removed } = cartSlice.actions
export const cartReducer = cartSlice.reducerImmer: в createSlice можно писать код, который выглядит как изменение состояния (state.push, item.qty++). RTK использует библиотеку Immer, которая превращает эти «мутации» в новый иммутабельный объект. Правила из Ссылки и иммутабельность остаются в силе — просто синтаксис короче.
Стор и типизированные хуки
// shared/store/store.ts
export const makeStore = () => configureStore({ reducer: { cart: cartReducer } })
export type AppStore = ReturnType<typeof makeStore>
export type RootState = ReturnType<AppStore['getState']>
export type AppDispatch = AppStore['dispatch']
export const useAppSelector = useSelector.withTypes<RootState>()
export const useAppDispatch = useDispatch.withTypes<AppDispatch>()'use client'
const count = useAppSelector(state => state.cart.length)
const dispatch = useAppDispatch()
<button onClick={() => dispatch(added(product.id))}>Добавить</button>Redux с Next.js App Router
- Стор создаётся на каждый запрос, а не как глобальная переменная модуля — иначе на сервере данные одного пользователя попадут к другому. Поэтому
makeStore()— функция. - Его кладут в Client Component
StoreProvider('use client'), созданный один раз черезuseRef/useState. - Server Components не могут читать стор — они получают данные напрямую.
Когда Redux Toolkit
| Да | Нет |
|---|---|
| большое приложение, много команд, полезны строгие правила | маленькое / среднее приложение |
| сложная логика с множеством переходов, нужны DevTools / time-travel | совсем немного глобального UI-состояния |
| существующий проект на Redux | состояние, которое на самом деле серверное |
Коротко
- Redux = один стор, действия, чистые редьюсеры; RTK — актуальный способ его писать.
createSlice+ Immer = короткий код, иммутабельный результат.- В Next: стор на запрос, внутри Client Component; для серверных данных → RTK Query.