state-management · 16 мин чтения

Zustand vs Redux vs Jotai в 2026: управление состоянием в React

Zustand Redux Jotai React управление состоянием Redux Toolkit Jotai атомы Zustand простота
Содержание

Управление состоянием в 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-соединении.

МетрикаZustandRedux ToolkitJotai
Размер бандла (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 из коробкиДаДаДа
DevToolsRedux DevTools (middleware)Redux DevTools (встроенные)React DevTools, jotai-devtools
Middleware/расширенияpersist, devtools, immerthunk, saga, RTK Queryutils, tRPC, immer, xstate, query
Поддержка SuspenseНетНетДа (встроенная)

Бенчмарки в реальных сценариях

Тесты производительности на реальных паттернах показывают следующую картину:

E-commerce корзина (Zustand):

  • Монолитный store: 75 мс среднее время рендера
  • Доменные store: 32 мс
  • Точечные селекторы: 18 мс

Форма с 50+ полями (Redux Toolkit):

  • Legacy connect HOC: 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/utilsatomWithStorage (persist), atomWithReducer, atomFamily, loadable, selectAtom
  • jotai-immer — мутабельные обновления атомов
  • jotai-tanstack-query — интеграция с TanStack Query
  • jotai-xstate — атомы на основе state machines
  • jotai-trpc — типобезопасный RPC
  • jotai-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 возможна инкрементально. Основные шаги:

  1. Создайте Zustand store, повторяющий структуру существующего Redux slice
  2. Перенесите логику reducers в функции внутри create()
  3. Замените useSelector на прямой вызов хука store
  4. Замените dispatch(action()) на прямой вызов метода store
  5. Удалите Provider после переноса последнего slice

С Redux на Jotai

Миграция на Jotai требует изменения ментальной модели:

  1. Разбейте состояние slice на независимые атомы
  2. Создайте производные атомы для вычисляемых значений
  3. Замените thunks на write-атомы
  4. Замените useSelector на useAtomValue
  5. Замените 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 крупными компаниями. Ни одна из них не является «неправильным» выбором — вопрос лишь в том, какая лучше соответствует вашей задаче.

Источники

  1. Zustand — официальный репозиторий GitHub
  2. Redux Toolkit — официальная документация
  3. Jotai — официальная документация
  4. State Management in 2026: Zustand vs Jotai vs Redux Toolkit vs Signals — DEV Community
  5. Zustand vs Redux Toolkit vs Jotai — Better Stack
  6. State Management in 2025: When to Use Context, Redux, Zustand, or Jotai — DEV Community
  7. Comparison — Jotai Official Docs
  8. RTK Query Overview — Redux Toolkit
  9. npm trends: jotai vs redux vs zustand
  10. Top 5 React State Management Tools Developers Actually Use in 2026 — Syncfusion
← Все статьи