Антон Ларичев

Введение
Управление состоянием — одна из ключевых задач при разработке React-приложений. В 2025 году выбор инструмента всё чаще сводится к двум решениям: проверенный временем Redux (в современной обёртке Redux Toolkit) и минималистичный Zustand. Оба подхода решают одну проблему, но делают это принципиально по-разному.
В этой статье разберём архитектурные различия, синтаксис, производительность и реальные сценарии использования. Цель — помочь выбрать подходящий инструмент именно под ваш проект.
Архитектурные различия
Redux построен на идее однонаправленного потока данных и иммутабельности. Действия (actions) описывают, что произошло, редьюсеры — как изменилось состояние. Это даёт предсказуемость, но требует много кода-обёрток.
Zustand отбрасывает концепцию редьюсеров и действий как обязательную. Хранилище — это просто объект с данными и методами. Подписка на изменения происходит через хук, а ререндер запускается только при изменении выбранной части состояния.
Создание стора: Redux Toolkit
import { createSlice, configureStore } from '@reduxjs/toolkit'
// Создаём слайс с начальным состоянием и редьюсерами
const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => {
// Immer позволяет писать мутабельный код
state.value += 1
},
addBy: (state, action: { payload: number }) => {
state.value += action.payload
},
},
})
export const { increment, addBy } = counterSlice.actions
export const store = configureStore({
reducer: { counter: counterSlice.reducer },
})
Использование в компоненте:
import { useSelector, useDispatch } from 'react-redux'
function Counter() {
// Получаем значение из стора
const value = useSelector((s: RootState) => s.counter.value)
const dispatch = useDispatch()
return (
<button onClick={() => dispatch(increment())}>
Значение: {value}
</button>
)
}
Создание стора: Zustand
import { create } from 'zustand'
// Типизируем состояние и методы
type CounterState = {
value: number
increment: () => void
addBy: (n: number) => void
}
export const useCounter = create<CounterState>((set) => ({
value: 0,
// Методы напрямую меняют состояние через set
increment: () => set((s) => ({ value: s.value + 1 })),
addBy: (n) => set((s) => ({ value: s.value + n })),
}))
Использование:
function Counter() {
// Селектор гарантирует ререндер только при изменении value
const value = useCounter((s) => s.value)
const increment = useCounter((s) => s.increment)
return (
<button onClick={increment}>
Значение: {value}
</button>
)
}
Разница очевидна: меньше файлов, меньше абстракций, нет необходимости в Provider.
Асинхронные операции
В Redux Toolkit для асинхронной логики используют createAsyncThunk или RTK Query. Это мощный инструмент с кешированием, инвалидацией и встроенной обработкой состояний загрузки.
import { createAsyncThunk } from '@reduxjs/toolkit'
export const fetchUser = createAsyncThunk(
'user/fetch',
async (id: string) => {
const res = await fetch(`/api/users/${id}`)
return res.json()
},
)
В Zustand нет встроенных средств для асинхронности — но они и не нужны. Любой метод стора может быть async:
export const useUser = create<UserState>((set) => ({
user: null,
loading: false,
fetchUser: async (id: string) => {
set({ loading: true })
const res = await fetch(`/api/users/${id}`)
// После запроса обновляем состояние
set({ user: await res.json(), loading: false })
},
}))
Для сложного кеширования с Zustand обычно комбинируют его с TanStack Query.
Производительность
Zustand выигрывает в простых сценариях за счёт точечных подписок: ререндерится только тот компонент, чей селектор вернул новое значение. Redux с useSelector работает аналогично, но дополнительные слои (middleware, devtools) создают накладные расходы.
В бенчмарках на 10 000 элементов разница между ними обычно в пределах погрешности. Решающим становится корректное использование селекторов, а не выбор библиотеки.
Когда выбрать Redux
Redux Toolkit стоит брать, если:
- В команде уже есть опыт с Redux и налаженный workflow
- Нужны строгие гарантии предсказуемости и аудит изменений
- Активно используется RTK Query для работы с API
- Проект большой, с десятками доменов состояния
Когда выбрать Zustand
Zustand подходит для:
- Стартапов и MVP, где важна скорость разработки
- Средних SPA с локальным состоянием в нескольких сторах
- Проектов, где сервер-стейт уже закрыт через TanStack Query или SWR
- Команд, которым важна минимальная кривая входа
Частые ошибки
Селектор возвращает новый объект на каждом рендере. В обеих библиотеках это вызывает лишние ререндеры. Используйте shallow или примитивные селекторы.
// Плохо: новый объект на каждом рендере
const { a, b } = useStore((s) => ({ a: s.a, b: s.b }))
// Хорошо: два отдельных селектора
const a = useStore((s) => s.a)
const b = useStore((s) => s.b)
Хранение серверного состояния в клиентском сторе. И Redux, и Zustand плохо приспособлены для кеширования запросов. Используйте RTK Query или TanStack Query.
Глобализация всего подряд. Не каждое состояние должно жить в сторе. Локальный useState часто остаётся лучшим выбором.
Игнорирование иммутабельности в Zustand. Хотя библиотека не заставляет писать иммутабельный код, прямые мутации могут привести к багам подписок.
Заключение
В 2025 году нет единственно правильного выбора. Redux Toolkit остаётся отличным решением для крупных корпоративных проектов с устоявшимися практиками. Zustand завоёвывает рынок там, где ценят простоту и скорость.
Практичный подход: для нового проекта без жёстких требований берите Zustand плюс TanStack Query — это покрывает 90% задач при минимуме кода. Если же у вас уже есть инфраструктура на Redux, миграция ради миграции не нужна — обе библиотеки прекрасно справляются со своей работой.






Комментарии
0