Назад
Назад к заметкам
· 4 мин чтения

Zustand: простой менеджер состояния для React

Zustand небольшая библиотека для управления глобальным состоянием в React, с минимумом кода, поддержкой middleware и оптимизированными ререндером.

reactstate-managementzustandhooks

Когда приложение растет, useState в каждом компоненте начинает раздуваться, а количество ререндеров растет. Часто приходиться писать контексты или подключать Redux. В таких случаях удобно использовать zustand легкую библиотеку, позволяющую держать глобальный стейт в пару строк кода.

Что такое zustand?

zustand (нем. “состояние”) небольшая библиотека для управления состоянием в React. В отличие от Redux, здесь нет экшенов, редьюсеров и оберток. Достаточно объявить обычную функцию, возвращающую объект со свойствами и методами. Библиотека работает через React-hooks, поэтому ее можно подключать к уже существующим компонентам.

Ключевые характеристики

  • Минимальный API один create и один хук useStore.
  • Мутируемый стиль пишете обычный код, а zustand под капотом делает иммутабельные обновления.
  • Оптимизированные ререндеры компонент обновляется только при изменении выбранных данных.
  • Middleware поддержка логирования, персистентности, devtools и пр.

Создание хранилища

Создание store требует одного вызова create. Внутри описываем начальное состояние и функции-мутаторы.

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

// простой store без middleware
export const useCartStore = create((set) => ({
  items: [],
  addItem: (item) => set((state) => ({ items: [...state.items, item] })),
  removeItem: (id) =>
    set((state) => ({ items: state.items.filter((i) => i.id !== id) })),
}));

// store c devtools и persist
export const usePersistedCart = create(
  devtools(
    persist(
      (set) => ({
        items: [],
        addItem: (item) => set((state) => ({ items: [...state.items, item] })),
        clear: () => set({ items: [] }),
      }),
      { name: "cart-storage" }
    )
  )
);

Первая версия хранит массив items и два метода. Вторая добавляет middleware: devtools позволяет просматривать состояние в Redux DevTools, persist сохраняет данные в localStorage.

Использование в компонентах

Хук useCartStore возвращает нужные части стейта. При использовании селектора компонент подписывается только на выбранные данные.

import React from "react";
import { useCartStore } from "./cartStore";

function Cart() {
  const items = useCartStore((state) => state.items);
  const removeItem = useCartStore((state) => state.removeItem);

  return (
    <ul>
      {items.map((item) => (
        <li key={item.id}>
          {item.name} {item.price}$
          <button onClick={() => removeItem(item.id)}>Удалить</button>
        </li>
      ))}
    </ul>
  );
}

Каждый вызов useCartStore с отдельным селектором создает независимую подписку, поэтому изменение items не заставит перерисовываться компоненты, которые используют только addItem.

Если нужны сразу несколько полей, их можно собрать в один объект:

const { items, addItem } = useCartStore((state) => ({
  items: state.items,
  addItem: state.addItem,
}));

Продвинутая работа: shallow и middleware

Сравнение по shallow

По умолчанию сравнение происходит по ссылке. При передаче массивов или объектов каждый ререндер будет срабатывать. Для избежания этого подключаем shallow из zustand/shallow.

import shallow from "zustand/shallow";

const [items, total] = useCartStore(
  (state) => [state.items, state.items.reduce((sum, i) => sum + i.price, 0)],
  shallow
);

shallow сравнивает массивы и объекты по поверхностным полям, перерисовывая компонент только при реальном изменении.

Пример middleware: логирование

import { subscribeWithSelector } from "zustand/middleware";
import create from "zustand";

export const useLogStore = create(
  subscribeWithSelector((set) => ({
    count: 0,
    inc: () => set((state) => ({ count: state.count + 1 })),
  }))
);

useLogStore.subscribe(
  (state) => state.count,
  (count) => console.log("count changed:", count)
);

subscribeWithSelector позволяет подписаться на конкретные части состояния и выполнить произвольный код при их изменении.

Сравнительная таблица

ФреймворкРазмер пакетаBoilerplateDevToolsPersistПодходит для больших приложений
Zustand~2 KBМинимальный✅ (middleware)✅ (middleware)Да
Redux Toolkit~10 KBСреднийДа
Context APIНетМаленькие проекты

Когда стоит выбрать zustand

  • Нужно быстро добавить глобальный стейт без экшенов и редьюсеров.
  • Приложение уже использует React-hooks, а дополнительный слой управления нежелателен.
  • Требуется контроль ререндеров через селекторы и shallow.
  • Нужно подключить devtools или персистентность без отдельной конфигурации.

Чеклист

  • Храните в стейте только данные; методы объявляйте в create.
  • Выбирайте минимальный набор полей в селекторах.
  • При работе с массивами/объектами используйте shallow.
  • В продакшене ограничьте middleware, которые логируют каждый апдейт.
  • Тестируйте store отдельно это обычный объект JavaScript.

Готов начать?

Выбери удобный способ связи — напиши напрямую или оставь заявку

Написать в Telegram

Отвечу в течение пары часов