Redux Toolkit и альтернативы: что выбрать и почему

Вопрос про выбор инструмента: интервьюеру важен не список библиотек, а критерии решения.

Redux Toolkit (RTK) — официальный набор поверх Redux: срезы состояния через createSlice, встроенный Immer, настроенный store и типизация из коробки.

Вопрос

«Какой state manager используете и почему? Что даёт Redux Toolkit по сравнению с классическим Redux? Какие есть альтернативы?»

Что на самом деле проверяет интервьюер

Умение обосновывать. Ответ «Redux, потому что стандарт» слаб так же, как «Redux устарел, все на Zustand». Ждут понимания, за какие свойства платят сложностью.

Развёрнутый ответ

Что RTK убрал из классического Redux

  • Ручные константы типов экшенов и switch в редьюсерах — теперь createSlice генерирует и то, и другое.
  • Ручное копирование объектов — внутри работает Immer.
  • Настройку store и middleware — configureStore сразу подключает thunk и dev-проверки.
  • Отдельную типизацию — типы выводятся из срезов.
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';

export const fetchCart = createAsyncThunk('cart/fetch', async (userId: string) => {
  const res = await fetch(`/api/cart/${userId}`);
  return res.json();
});

const cartSlice = createSlice({
  name: 'cart',
  initialState: { items: [] as Item[], status: 'idle' as Status },
  reducers: {
    // выглядит как мутация, но Immer превращает это в новый объект
    add(state, action) {
      state.items.push(action.payload);
    },
    remove(state, action) {
      state.items = state.items.filter((i) => i.id !== action.payload);
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchCart.pending, (state) => { state.status = 'loading'; })
      .addCase(fetchCart.fulfilled, (state, action) => {
        state.status = 'ready';
        state.items = action.payload;
      });
  },
});

export const { add, remove } = cartSlice.actions;
export default cartSlice.reducer;

Селекторы и лишние рендеры

// ПЛОХО: новый массив на каждом вызове — компонент рендерится на любое изменение store
const visible = useSelector((s) => s.cart.items.filter((i) => !i.hidden));

// ХОРОШО: мемоизированный селектор
import { createSelector } from '@reduxjs/toolkit';

const selectVisibleItems = createSelector(
  (s) => s.cart.items,
  (items) => items.filter((i) => !i.hidden)
);

const visible = useSelector(selectVisibleItems);

Это один из самых частых практических вопросов: useSelector сравнивает результат строгим равенством, поэтому селектор обязан возвращать стабильную ссылку.

Альтернативы — обзорно

ИнструментМодельКогда уместен
Redux Toolkitединый store, экшены, редьюсерыбольшая команда, строгие соглашения, middleware, отладка
Zustandstore как хук, без провайдеранебольшой и средний проект, минимум кода
Jotai / Recoilатомы, состояние снизу вверхмного мелких независимых кусочков состояния
MobXнаблюдаемые объекты, автоматические подпискикоманда с ООП-опытом, сложные модели предметной области
TanStack Query / RTK Queryкэш серверных данныхпочти всегда, когда есть API
// Zustand для сравнения: store без провайдера и без boilerplate
import { create } from 'zustand';

export const useCart = create<CartState>((set) => ({
  items: [],
  add: (item) => set((s) => ({ items: [...s.items, item] })),
  clear: () => set({ items: [] }),
}));

// в компоненте подписываемся точечно, только на нужный срез
const count = useCart((s) => s.items.length);

Особенности мобильного контекста

  • Persist. Приложение выгружается системой из памяти, поэтому корзину, черновики и токен обычно сохраняют: redux-persist поверх AsyncStorage или MMKV.
  • Оффлайн. Мобильная сеть пропадает постоянно — очередь отложенных действий и оптимистичные обновления обсуждают на собеседовании часто.
  • Размер store. Хранить в нём тяжёлые списки и base64-картинки — быстрый путь к нехватке памяти на бюджетных устройствах.

Типичные ошибки кандидатов

  • Не могут объяснить, почему в RTK допустима «мутация» — не знают про Immer.
  • Пишут селекторы, возвращающие новые массивы, и потом жалуются на тормоза.
  • Кладут в глобальный store серверные данные и вручную пишут кэш и инвалидацию.
  • Обосновывают выбор модой, а не свойствами проекта: размером команды, сроком жизни кода, требованиями к отладке.

Как ответить кратко

«Redux Toolkit — это Redux без boilerplate: createSlice сам генерирует экшены и редьюсеры, внутри Immer, поэтому редьюсер выглядит как мутация, а на выходе получается новый неизменяемый state; configureStore настраивает middleware и типизацию. Важная деталь — селекторы: useSelector сравнивает результат по ссылке, поэтому производные значения считаю через createSelector. Из альтернатив: Zustand для компактных проектов, Jotai для атомарного состояния, MobX для сложных моделей. Серверные данные в любом случае держу в RTK Query или TanStack Query, а не в общем store. На мобильном добавляю persist и продумываю оффлайн.»

Проверьте себя
1. Почему в Redux Toolkit можно писать state.items.push(item) прямо в редьюсере?
ARTK отказался от иммутабельности в пользу скорости
BВнутри работает Immer: изменения применяются к черновику и превращаются в новый неизменяемый объект
CЭто допустимо только в асинхронных редьюсерах
DМассивы в Redux считаются исключением из правила иммутабельности
2. Что произойдёт, если селектор возвращает новый объект при каждом вызове?
ARedux закэширует результат автоматически
BКомпонент будет перерисовываться при любом изменении store, потому что useSelector сравнивает результат по ссылке
CПриложение упадёт с ошибкой сравнения
DСелектор будет вызван только один раз
3. В каком случае Zustand — разумный выбор вместо Redux Toolkit?
AКогда нужен минимум кода и хватает простых store без строгих соглашений и развитого middleware
BКогда в команде больше десяти человек и требуется строгая структура
CКогда нужен полноценный DevTools с time-travel отладкой
DКогда состояние преимущественно серверное