Назад к блогу
Frontend

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

Сравнение Zustand и Redux для управления глобальным состоянием: store, actions, middleware, persist и лучшие практики.

5 января 2026 г.
11 мин чтения
237 просмотров
MOLOTILO

MOLOTILO DIGITAL

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

Зачем нужен State Management

Когда приложение растёт, передача состояния через props становится неудобной (prop drilling). Библиотеки управления состоянием решают эту проблему, предоставляя глобальный store.

Zustand — простота и минимализм

Zustand — легковесная библиотека (1KB) с простым API:

import { create } from 'zustand';

// Определение store
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 existingItem = state.items.find(i => i.id === item.id);
    
    if (existingItem) {
      return {
        items: state.items.map(i =>
          i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
        ),
        total: state.total + item.price
      };
    }
    
    return {
      items: [...state.items, { ...item, quantity: 1 }],
      total: state.total + item.price
    };
  }),
  
  removeItem: (id) => set((state) => {
    const item = state.items.find(i => i.id === id);
    return {
      items: state.items.filter(i => i.id !== id),
      total: state.total - (item ? item.price * item.quantity : 0)
    };
  }),
  
  clearCart: () => set({ items: [], total: 0 })
}));

// Использование в компонентах
function CartButton() {
  const itemCount = useCartStore((state) => state.items.length);
  return <button>Cart ({itemCount})</button>;
}

function CartTotal() {
  const total = useCartStore((state) => state.total);
  return <div>Total: {total}₽</div>;
}

function AddToCartButton({ product }: { product: Product }) {
  const addItem = useCartStore((state) => state.addItem);
  return <button onClick={() => addItem(product)}>Add to Cart</button>;
}

Zustand Middleware

import { create } from 'zustand';
import { persist, devtools } from 'zustand/middleware';

// Persist — сохранение в localStorage
const useCartStore = create<CartStore>()(
  devtools(
    persist(
      (set) => ({
        items: [],
        total: 0,
        addItem: (item) => set((state) => ({ ... })),
      }),
      {
        name: 'cart-storage', // ключ в localStorage
        partialize: (state) => ({ items: state.items }), // что сохранять
      }
    ),
    { name: 'CartStore' } // имя в DevTools
  )
);

// Immer middleware для иммутабельных обновлений
import { immer } from 'zustand/middleware/immer';

const useStore = create<State>()(
  immer((set) => ({
    users: [],
    addUser: (user) => set((state) => {
      state.users.push(user); // Мутация безопасна с immer
    }),
  }))
);

Redux Toolkit — стандарт индустрии

Redux Toolkit упрощает работу с Redux:

import { createSlice, configureStore, PayloadAction } from '@reduxjs/toolkit';

// Slice — часть состояния
const cartSlice = createSlice({
  name: 'cart',
  initialState: {
    items: [] as CartItem[],
    total: 0
  },
  reducers: {
    addItem: (state, action: PayloadAction<CartItem>) => {
      const existingItem = state.items.find(i => i.id === action.payload.id);
      
      if (existingItem) {
        existingItem.quantity += 1;
      } else {
        state.items.push({ ...action.payload, quantity: 1 });
      }
      
      state.total += action.payload.price;
    },
    removeItem: (state, action: PayloadAction<string>) => {
      const index = state.items.findIndex(i => i.id === action.payload);
      if (index !== -1) {
        state.total -= state.items[index].price * state.items[index].quantity;
        state.items.splice(index, 1);
      }
    },
    clearCart: (state) => {
      state.items = [];
      state.total = 0;
    }
  }
});

export const { addItem, removeItem, clearCart } = cartSlice.actions;

// Store
const store = configureStore({
  reducer: {
    cart: cartSlice.reducer,
    // другие slices
  }
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

// Типизированные хуки
import { useDispatch, useSelector, TypedUseSelectorHook } from 'react-redux';

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

// Использование
function CartButton() {
  const itemCount = useAppSelector((state) => state.cart.items.length);
  return <button>Cart ({itemCount})</button>;
}

function AddToCartButton({ product }: { product: Product }) {
  const dispatch = useAppDispatch();
  return (
    <button onClick={() => dispatch(addItem(product))}>
      Add to Cart
    </button>
  );
}

Redux Async Actions (RTK Query)

import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';

const api = createApi({
  reducerPath: 'api',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  tagTypes: ['Products', 'Cart'],
  endpoints: (builder) => ({
    getProducts: builder.query<Product[], void>({
      query: () => 'products',
      providesTags: ['Products']
    }),
    addToCart: builder.mutation<Cart, { productId: string }>({
      query: (body) => ({
        url: 'cart',
        method: 'POST',
        body
      }),
      invalidatesTags: ['Cart']
    })
  })
});

export const { useGetProductsQuery, useAddToCartMutation } = api;

// Использование
function ProductList() {
  const { data: products, isLoading, error } = useGetProductsQuery();
  const [addToCart] = useAddToCartMutation();
  
  if (isLoading) return <Spinner />;
  
  return (
    <ul>
      {products?.map(product => (
        <li key={product.id}>
          {product.name}
          <button onClick={() => addToCart({ productId: product.id })}>
            Add
          </button>
        </li>
      ))}
    </ul>
  );
}

Сравнение Zustand и Redux

КритерийZustandRedux Toolkit
Размер~1KB~10KB
BoilerplateМинимумБольше
DevToolsЧерез middlewareВстроено
AsyncПросто в actionsRTK Query / Thunk
ЭкосистемаРастётОгромная
Для проектаМалый/среднийЛюбой размер

Заключение

Zustand — отличный выбор для небольших и средних проектов благодаря простоте. Redux Toolkit подходит для крупных приложений с командой, где важны DevTools и экосистема. Оба решения типобезопасны и хорошо работают с TypeScript.

Начните с Zustand для простоты. Переходите на Redux, когда нужны RTK Query, сложные middleware или большая команда.

ReactZustandReduxState Management

Понравилась статья?

Подпишитесь на наш блог, чтобы не пропустить новые материалы