Zustand vs Redux vs Jotai в 2026: управление состоянием в React
Содержание
Управление состоянием в React-приложениях в 2026 году больше не сводится к вопросу «Redux или не Redux». Экосистема выросла, появились легковесные альтернативы, а разработчики всё чаще выбирают инструмент не по популярности, а по конкретным задачам проекта. Три библиотеки — Zustand, Redux Toolkit и Jotai — занимают ключевые позиции на рынке, но решают проблему управления состоянием принципиально разными способами.
Zustand предлагает минималистичный API без провайдеров и boilerplate. Redux Toolkit остаётся стандартом для крупных enterprise-приложений с предсказуемым потоком данных. Jotai реализует атомарный подход, вдохновлённый Recoil, но без его архитектурных ограничений. Каждая из библиотек имеет свою философию, свои сильные стороны и свои компромиссы.
Эта статья предназначена для React-разработчиков, тимлидов и архитекторов, которым нужно выбрать решение для нового проекта или оценить целесообразность миграции. Мы разберём архитектуру, производительность, размер бандла, примеры кода и реальные сценарии использования каждой библиотеки — без маркетингового тумана, с конкретными цифрами и рекомендациями.
Краткий обзор участников
Zustand — простота без компромиссов
Zustand (в переводе с немецкого — «состояние») — библиотека управления состоянием, созданная командой Pmndrs (Poimandres), которая также разрабатывает React Three Fiber, Jotai и Valtio. Текущая стабильная версия — Zustand 5.0.x. На GitHub проект набрал свыше 57 000 звёзд, а еженедельные загрузки из npm превышают 14,7 миллиона.
Философия Zustand: минимальный API, нулевой boilerplate, отсутствие обязательных провайдеров. Вы создаёте store как обычную функцию, а компоненты подписываются на конкретные срезы состояния через хуки. Zustand 5 построен на useSyncExternalStore из React 18, что обеспечивает корректное поведение в concurrent mode и предсказуемое распространение состояния.
Ключевые нововведения Zustand 5: улучшенная совместимость с concurrent rendering, хук useShallow для поверхностного сравнения вместо кастомных equality-функций, обновлённый persist-middleware и сокращённый размер пакета через внутренний рефакторинг.
Redux Toolkit — проверенный enterprise-стандарт
Redux Toolkit (RTK) — официальный, рекомендованный способ использования Redux. Разрабатывается командой Redux под руководством Марка Эриксона (Mark Erikson). Текущая версия — RTK 2.11.x. На GitHub у Redux Toolkit свыше 11 100 звёзд, а пакет @reduxjs/toolkit скачивается более 8 миллионов раз в неделю. Связанный пакет react-redux — более 12,4 миллиона еженедельных загрузок.
Redux Toolkit решил главную проблему «классического» Redux — чрезмерный boilerplate. createSlice объединяет action creators и reducers, createAsyncThunk упрощает асинхронные операции, а createEntityAdapter стандартизирует работу с нормализованными данными. RTK Query — встроенное решение для data fetching с кэшированием, автоматическим рефетчингом и оптимистичными обновлениями.
Redux остаётся единственной библиотекой с полноценным time-travel debugging через Redux DevTools, что критично для отладки сложных бизнес-процессов в enterprise-приложениях.
Jotai — атомарное состояние
Jotai (в переводе с японского — «состояние») — ещё один проект команды Pmndrs, реализующий атомарную модель управления состоянием. Текущая версия — Jotai 2.16.x. На GitHub — свыше 20 800 звёзд, еженедельные загрузки из npm — около 2,5 миллиона.
Jotai создан как «минималистичный API для Recoil». Вместо единого store, состояние разбивается на независимые атомы — мельчайшие единицы состояния. Компоненты подписываются на конкретные атомы, и React перерисовывает только те компоненты, чьи атомы изменились. Производные атомы автоматически пересчитываются при изменении зависимостей — как формулы в электронной таблице.
Jotai v2 полностью совместим с React 18 и поддерживает Suspense из коробки, что делает его отличным выбором для приложений с асинхронной загрузкой данных. Store-интерфейс позволяет работать с атомами за пределами React-компонентов.
Архитектура и философия
Архитектурные различия между тремя библиотеками определяют всё: от количества написанного кода до паттернов масштабирования и модели переиспользования состояния.
Zustand: единый store, минимальный API
Zustand использует модель единого store (хотя ничто не мешает создать несколько). Store — это обычный JavaScript-объект с методами для чтения и изменения состояния. Нет actions, нет reducers, нет dispatch — вы просто вызываете set() для обновления.
import { create } from 'zustand';
interface CartStore {
items: CartItem[];
total: number;
addItem: (item: CartItem) => void;
removeItem: (id: string) => void;
clearCart: () => void;
}
const useCartStore = create<CartStore>((set, get) => ({
items: [],
total: 0,
addItem: (item) =>
set((state) => {
const items = [...state.items, item];
return { items, total: items.reduce((sum, i) => sum + i.price, 0) };
}),
removeItem: (id) =>
set((state) => {
const items = state.items.filter((i) => i.id !== id);
return { items, total: items.reduce((sum, i) => sum + i.price, 0) };
}),
clearCart: () => set({ items: [], total: 0 }),
}));
Компонент подписывается на конкретный срез состояния через селектор:
const CartTotal = () => {
const total = useCartStore((state) => state.total);
return <span>Итого: {total} ₽</span>;
};
Ключевое преимущество: компонент CartTotal перерисовывается только при изменении total, а не при любом изменении store. Провайдер не нужен — store доступен глобально.
Redux Toolkit: предсказуемый поток данных
Redux Toolkit сохраняет классическую архитектуру Redux — однонаправленный поток данных через actions и reducers — но радикально сокращает boilerplate:
import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';
interface CartState {
items: CartItem[];
total: number;
}
const cartSlice = createSlice({
name: 'cart',
initialState: { items: [], total: 0 } as CartState,
reducers: {
addItem: (state, action: PayloadAction<CartItem>) => {
state.items.push(action.payload); // Immer позволяет мутировать
state.total = state.items.reduce((sum, i) => sum + i.price, 0);
},
removeItem: (state, action: PayloadAction<string>) => {
state.items = state.items.filter((i) => i.id !== action.payload);
state.total = state.items.reduce((sum, i) => sum + i.price, 0);
},
clearCart: (state) => {
state.items = [];
state.total = 0;
},
},
});
export const { addItem, removeItem, clearCart } = cartSlice.actions;
const store = configureStore({
reducer: { cart: cartSlice.reducer },
});
Использование в компоненте:
import { useSelector, useDispatch } from 'react-redux';
import { addItem } from './cartSlice';
const CartTotal = () => {
const total = useSelector((state: RootState) => state.cart.total);
return <span>Итого: {total} ₽</span>;
};
const AddButton = ({ item }: { item: CartItem }) => {
const dispatch = useDispatch();
return <button onClick={() => dispatch(addItem(item))}>Добавить</button>;
};
Redux требует обёртки приложения в <Provider store={store}>, но это даёт важное преимущество: полная изоляция состояния, что критично для тестирования и server-side rendering.
Jotai: атомарная композиция
Jotai разбивает состояние на атомы — мельчайшие независимые единицы. Атомы можно комбинировать, создавая производные значения:
import { atom } from 'jotai';
// Базовые атомы
const itemsAtom = atom<CartItem[]>([]);
// Производный атом — пересчитывается автоматически
const totalAtom = atom((get) =>
get(itemsAtom).reduce((sum, item) => sum + item.price, 0)
);
// Write-атом для действий
const addItemAtom = atom(null, (get, set, newItem: CartItem) => {
set(itemsAtom, [...get(itemsAtom), newItem]);
});
const removeItemAtom = atom(null, (get, set, id: string) => {
set(
itemsAtom,
get(itemsAtom).filter((item) => item.id !== id)
);
});
const clearCartAtom = atom(null, (_get, set) => {
set(itemsAtom, []);
});
Использование в компоненте:
import { useAtomValue, useSetAtom } from 'jotai';
const CartTotal = () => {
const total = useAtomValue(totalAtom);
return <span>Итого: {total} ₽</span>;
};
const AddButton = ({ item }: { item: CartItem }) => {
const addItem = useSetAtom(addItemAtom);
return <button onClick={() => addItem(item)}>Добавить</button>;
};
Jotai автоматически отслеживает зависимости между атомами. Если itemsAtom изменился, totalAtom пересчитается, а компоненты, подписанные на totalAtom, перерисуются. Компоненты, подписанные только на другие атомы, останутся нетронутыми.
Производительность и размер бандла
Размер бандла и скорость обновлений — критичные метрики для production-приложений. Разница в 12 КБ может означать 100 мс дополнительной загрузки на 3G-соединении.
| Метрика | Zustand | Redux Toolkit | Jotai |
|---|---|---|---|
| Размер бандла (min+gzip) | ~3 КБ | ~15 КБ (с react-redux) | ~4 КБ |
| Одиночное обновление состояния | 12 мс | 18 мс | 14 мс |
| Память (1000 компонентов) | 2,1 МБ | 3,2 МБ | 1,8 МБ |
| Время парсинга бандла | 8 мс | 34 мс | 9 мс |
| Boilerplate для store | ~12 строк | ~28 строк | ~11 строк |
| Провайдер обязателен | Нет | Да | Опционально |
| TypeScript из коробки | Да | Да | Да |
| DevTools | Redux DevTools (middleware) | Redux DevTools (встроенные) | React DevTools, jotai-devtools |
| Middleware/расширения | persist, devtools, immer | thunk, saga, RTK Query | utils, tRPC, immer, xstate, query |
| Поддержка Suspense | Нет | Нет | Да (встроенная) |
Бенчмарки в реальных сценариях
Тесты производительности на реальных паттернах показывают следующую картину:
E-commerce корзина (Zustand):
- Монолитный store: 75 мс среднее время рендера
- Доменные store: 32 мс
- Точечные селекторы: 18 мс
Форма с 50+ полями (Redux Toolkit):
- Legacy
connectHOC: 280 мс - Современные хуки + мемоизированные селекторы: 95 мс
- Entity adapter с точечной подпиской: 45 мс
1000 компонентов с частыми обновлениями:
- React Context (один контекст): 350 мс
- React Context (разделённые контексты): 120 мс
- Zustand: 85 мс
- Jotai: 78 мс
- Redux Toolkit: 95 мс
Jotai показывает лучшую производительность при большом количестве независимых обновлений благодаря атомарной подписке. Zustand выигрывает в простоте оптимизации — достаточно правильно написать селектор. Redux Toolkit требует больше усилий для оптимизации, но предоставляет мощные инструменты для этого (reselect, entity adapter).
Работа с асинхронными данными
Асинхронные операции — загрузка данных с API, отправка форм, вебсокеты — ключевая часть любого приложения. Каждая библиотека подходит к этой задаче по-своему.
Zustand: прямолинейный async/await
Zustand не навязывает специальных абстракций для async-операций. Вы просто пишете асинхронные функции внутри store:
const useUserStore = create<UserStore>((set) => ({
users: [],
loading: false,
error: null,
fetchUsers: async () => {
set({ loading: true, error: null });
try {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Ошибка загрузки');
const users = await response.json();
set({ users, loading: false });
} catch (error) {
set({ error: (error as Error).message, loading: false });
}
},
}));
// В компоненте
const UserList = () => {
const { users, loading, fetchUsers } = useUserStore();
useEffect(() => {
fetchUsers();
}, [fetchUsers]);
if (loading) return <Spinner />;
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
};
Простота — и преимущество, и ограничение. Для сложных сценариев (дедупликация запросов, кэширование, оптимистичные обновления) придётся либо писать свою логику, либо комбинировать Zustand с TanStack Query.
Redux Toolkit: createAsyncThunk и RTK Query
Redux Toolkit предлагает два уровня абстракции. createAsyncThunk — для базовых async-операций:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const fetchUsers = createAsyncThunk(
'users/fetchUsers',
async (_, { rejectWithValue }) => {
try {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Ошибка сервера');
return await response.json();
} catch (error) {
return rejectWithValue((error as Error).message);
}
}
);
const usersSlice = createSlice({
name: 'users',
initialState: { list: [], loading: false, error: null as string | null },
reducers: {},
extraReducers: (builder) => {
builder
.addCase(fetchUsers.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(fetchUsers.fulfilled, (state, action) => {
state.list = action.payload;
state.loading = false;
})
.addCase(fetchUsers.rejected, (state, action) => {
state.error = action.payload as string;
state.loading = false;
});
},
});
RTK Query — полноценное решение для data fetching с автоматическим кэшированием:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
tagTypes: ['Users'],
endpoints: (builder) => ({
getUsers: builder.query<User[], void>({
query: () => 'users',
providesTags: ['Users'],
}),
addUser: builder.mutation<User, Partial<User>>({
query: (body) => ({ url: 'users', method: 'POST', body }),
invalidatesTags: ['Users'], // Автоматический рефетч
}),
}),
});
export const { useGetUsersQuery, useAddUserMutation } = api;
RTK Query обеспечивает дедупликацию запросов, кэширование по тегам, автоматическую инвалидацию, оптимистичные обновления и стриминг через вебсокеты. Однако он жёстко привязан к Redux-экосистеме.
Jotai: асинхронные атомы и Suspense
Jotai интегрируется с React Suspense из коробки. Асинхронные атомы — это атомы, возвращающие промисы:
import { atom } from 'jotai';
// Асинхронный read-атом — работает с Suspense
const usersAtom = atom(async () => {
const response = await fetch('/api/users');
if (!response.ok) throw new Error('Ошибка загрузки');
return response.json() as Promise<User[]>;
});
// Write-атом с AbortController
const fetchUsersAtom = atom(null, async (get, set, _arg, { signal }) => {
const response = await fetch('/api/users', { signal });
const users = await response.json();
set(usersListAtom, users);
});
Использование с Suspense:
import { useAtomValue } from 'jotai';
import { Suspense } from 'react';
const UserList = () => {
const users = useAtomValue(usersAtom); // Suspense обрабатывает загрузку
return <ul>{users.map((u) => <li key={u.id}>{u.name}</li>)}</ul>;
};
// В родительском компоненте
const App = () => (
<Suspense fallback={<Spinner />}>
<UserList />
</Suspense>
);
Jotai v2 поддерживает AbortController в write-атомах: если зависимость изменилась до завершения async-операции, предыдущий запрос отменяется автоматически. Это элегантное решение проблемы race conditions.
Middleware и расширяемость
Zustand middleware
Zustand предоставляет систему middleware через композицию функций:
import { create } from 'zustand';
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware';
import { immer } from 'zustand/middleware/immer';
const useStore = create<StoreState>()(
devtools(
persist(
immer(
subscribeWithSelector((set) => ({
count: 0,
increment: () => set((state) => { state.count += 1; }),
}))
),
{ name: 'app-storage' }
),
{ name: 'AppStore' }
)
);
Встроенные middleware: devtools (Redux DevTools), persist (localStorage/sessionStorage), immer (мутабельные обновления), subscribeWithSelector (подписка на изменения вне React).
Redux Toolkit middleware
Redux middleware — зрелая и мощная система. RTK включает thunk по умолчанию, а архитектура позволяет подключать любые middleware:
const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.concat(api.middleware) // RTK Query
.concat(loggerMiddleware) // Кастомный логгер
.concat(analyticsMiddleware), // Аналитика
});
Redux middleware перехватывают каждый action, что позволяет реализовать логирование, аналитику, авторизацию, rate limiting и любую другую cross-cutting логику. Ни Zustand, ни Jotai не предоставляют аналогичного уровня контроля над потоком данных.
Jotai утилиты и интеграции
Jotai расширяется через отдельные пакеты:
jotai/utils—atomWithStorage(persist),atomWithReducer,atomFamily,loadable,selectAtomjotai-immer— мутабельные обновления атомовjotai-tanstack-query— интеграция с TanStack Queryjotai-xstate— атомы на основе state machinesjotai-trpc— типобезопасный RPCjotai-optics— линзы для работы с вложенными структурами
Пример persist через atomWithStorage:
import { atomWithStorage } from 'jotai/utils';
const themeAtom = atomWithStorage('theme', 'light'); // Автоматически сохраняется в localStorage
Когда выбрать Zustand
Zustand — оптимальный выбор для большинства React-проектов в 2026 году. Выбирайте Zustand, когда:
- Средний или крупный проект без legacy Redux: если вы начинаете с нуля и не нуждаетесь в строгих архитектурных паттернах Redux, Zustand даст максимальную продуктивность при минимальном boilerplate.
- Важен размер бандла: 3 КБ против 15 КБ — разница ощутима на мобильных устройствах и в регионах с медленным интернетом.
- Команда ценит простоту: API Zustand можно освоить за 15 минут. Нет провайдеров, нет dispatch, нет action types — меньше концепций для новых разработчиков.
- Микрофронтенды: отсутствие обязательного Provider делает Zustand идеальным для модульных архитектур.
- Комбинация с TanStack Query: Zustand для клиентского состояния + TanStack Query для серверного — популярная и эффективная комбинация.
Zustand не подходит, если вам критичен time-travel debugging, нужна строгая архитектура с middleware-цепочками или команда уже хорошо знает Redux.
Когда выбрать Redux Toolkit
Redux Toolkit остаётся лучшим выбором для enterprise-приложений:
- Крупная команда (10+ разработчиков): стандартизированные паттерны Redux обеспечивают единообразие кода. Каждый разработчик знает, где искать логику — в slice, в thunk, в middleware.
- Сложная бизнес-логика: middleware-архитектура позволяет реализовать cross-cutting concerns (логирование, аналитика, авторизация) без дублирования кода.
- Time-travel debugging: Redux DevTools с полной историей actions — незаменимый инструмент при отладке сложных сценариев.
- RTK Query как замена TanStack Query: если вы уже используете Redux, RTK Query встраивается органично и не требует дополнительных зависимостей.
- Серверный рендеринг: Provider-архитектура Redux обеспечивает чистую изоляцию состояния между запросами.
Redux Toolkit избыточен для простых приложений, лендингов и проектов, где основная часть состояния — серверные данные.
Когда выбрать Jotai
Jotai раскрывается в определённых архитектурных сценариях:
- Сложные взаимозависимости состояний: формы с зависимыми полями, таблицы с вычисляемыми ячейками, дашборды с фильтрами — везде, где одно значение влияет на множество других.
- Приложения с React Suspense: Jotai — единственная из трёх библиотек с нативной поддержкой Suspense для асинхронных данных.
- Тонкая гранулярность обновлений: если приложение содержит сотни компонентов, подписанных на разные части состояния, атомарная модель Jotai обеспечивает минимальное количество перерисовок.
- Code splitting: атомы определяются в модулях, а не в центральном store, что упрощает разделение кода.
- Прототипирование: начать с Jotai можно с одной строки (
const countAtom = atom(0)), а затем масштабировать через композицию атомов.
Jotai не подходит, если команда привыкла к централизованному store и не готова менять ментальную модель, или если нужен мощный DevTools-опыт уровня Redux.
Комбинирование подходов
В 2026 году всё чаще встречается практика комбинирования state-менеджеров в одном проекте:
Серверное состояние → TanStack Query / RTK Query
Глобальное UI-состояние → Zustand
Локальное сложное состояние → Jotai
Формы → React Hook Form + Zod
URL-состояние → nuqs / useSearchParams
Такой подход позволяет использовать каждый инструмент для задачи, к которой он лучше всего приспособлен, вместо попытки решить всё одной библиотекой.
Миграция между библиотеками
С Redux на Zustand
Миграция с Redux на Zustand возможна инкрементально. Основные шаги:
- Создайте Zustand store, повторяющий структуру существующего Redux slice
- Перенесите логику reducers в функции внутри
create() - Замените
useSelectorна прямой вызов хука store - Замените
dispatch(action())на прямой вызов метода store - Удалите Provider после переноса последнего slice
С Redux на Jotai
Миграция на Jotai требует изменения ментальной модели:
- Разбейте состояние slice на независимые атомы
- Создайте производные атомы для вычисляемых значений
- Замените thunks на write-атомы
- Замените
useSelectorнаuseAtomValue - Замените
dispatchнаuseSetAtom
Заключение
Выбор между Zustand, Redux Toolkit и Jotai в 2026 году определяется не абстрактными бенчмарками, а конкретными потребностями проекта.
Zustand — выбор по умолчанию для большинства React-проектов. Минимальный API, крошечный бандл, отсутствие boilerplate и провайдеров. Если у вас нет специфических требований, начните с Zustand.
Redux Toolkit — стандарт для enterprise. Строгая архитектура, мощные DevTools, RTK Query и огромная экосистема. Если в команде 10+ разработчиков и проекту нужна предсказуемость на годы вперёд, Redux Toolkit — безопасный выбор.
Jotai — лучший вариант для приложений со сложными зависимостями между состояниями и потребностью в максимально гранулярных обновлениях. Нативная поддержка Suspense и элегантная модель производных атомов делают Jotai незаменимым в определённых архитектурных сценариях.
Все три библиотеки активно развиваются, имеют зрелую TypeScript-поддержку и используются в production крупными компаниями. Ни одна из них не является «неправильным» выбором — вопрос лишь в том, какая лучше соответствует вашей задаче.
Источники
- Zustand — официальный репозиторий GitHub
- Redux Toolkit — официальная документация
- Jotai — официальная документация
- State Management in 2026: Zustand vs Jotai vs Redux Toolkit vs Signals — DEV Community
- Zustand vs Redux Toolkit vs Jotai — Better Stack
- State Management in 2025: When to Use Context, Redux, Zustand, or Jotai — DEV Community
- Comparison — Jotai Official Docs
- RTK Query Overview — Redux Toolkit
- npm trends: jotai vs redux vs zustand
- Top 5 React State Management Tools Developers Actually Use in 2026 — Syncfusion