В React-экосистеме управление состоянием часто напоминает выбор оружия для битвы: Redux с его тяжелой артиллерией, Context API для легких стычек или MobX со своей магией прокси. Однако в этой гонке инструментов Zustand (нем. «состояние») остается недооцененной жемчужиной, предлагающей элегантную простоту без компромиссов в эффективности. Разберем, почему он заслуживает места в вашем арсенале и как извлечь из него максимум.
Почему Zustand, а не очередной Context?
Проблема стандартного Context API React – производительность при частых обновлениях. Любое изменение значения контекста приводит к повторному рендеру всех компонентов, подписанных на этот контекст, даже если они используют лишь неизменившуюся часть данных. Работает это так:
const UserContext = createContext();
function App() {
const [user, setUser] = useState({ name: 'Alex', theme: 'dark' });
// Обновил имя? Перерендерятся ВСЕ потребители, включая ThemeSwitcher!
return (
<UserContext.Provider value={{ user, setUser }}>
<Profile />
<ThemeSwitcher />
</UserContext.Provider>
);
}
function ThemeSwitcher() {
const { user } = useContext(UserContext);
// Использует ТОЛЬКО user.theme, но рендерится при изменении user.name!
return <button>Текущая тема: {user.theme}</button>;
}
Zustand решает это через точечную подписку компонентов только на необходимые им части состояния, используя механизм селекторов. Компонент перерисовывается только когда изменяется выбранная им часть стора, а не весь объект целиком.
Основы: Создаем Стройный Store
Установка тривиальна: npm install zustand
. Определим первый стор:
import create from 'zustand';
// Типизация для TypeScript (строго рекомендуется!)
interface CounterState {
count: number;
increment: () => void;
decrement: () => void;
reset: () => void;
}
// Хук `create` возвращает сам хук для использования в компонентах
const useCounterStore = create<CounterState>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}));
Ключевой момент: Функции обновления (increment
, decrement
, reset
) включены в сам стор. Это не только удобно (все связанное с логикой счетчика живет вместе), но и позволяет избежать проблем со стабильностью ссылок. set
принимает функцию-редьюсер или новый объект состояния и иммутабельно объединяет изменения с предыдущим состоянием (аналог setState
в классах React).
Использование в Компоненте: Локальное Ощущение, Глобальная Мощь
Подписываем компонент на нужные части стора с помощью селекторов:
function CounterDisplay() {
// Подписываемся ТОЛЬКО на `count`
const count = useCounterStore((state) => state.count);
return <div>Текущее значение: {count}</div>;
}
function CounterControls() {
// Подписываемся ТОЛЬКО на функции
const { increment, decrement, reset } = useCounterStore();
return (
<div>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
<button onClick={reset}>Сбросить</button>
</div>
);
}
Заметка: CounterControls
не перерендерится при изменении count
, потому что он не подписан на эту величину – только на стабильные ссылки функций. CounterDisplay
перерисуется только когда меняется count
.
Производительность Продвинутого Уровня: Middleware и Разбиение Моделей
Zustand блистает своей расширяемостью через middleware. Нужно логирование действий? Легко:
const useCounterStore = create<CounterState>(
// Middleware логирования
log(
(set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
// ...
}),
true // логировать только изменения?
)
);
// Где `log` - это кастомный middleware, или можно использовать готовый zustand/middleware
import { devtools } from 'zustand/middleware'; // Интеграция с Redux DevTools
const useStore = create(devtools(myStoreFunc));
Для сложных приложений состояний избегайте одного монолитного стора. Разделяйте на доменные модели – отдельные сторы для пользователя, настроек, корзины и т.д. Zustand не навязывает структуру, вы управляете масштабированием.
// AuthStore.ts
export const useAuthStore = create<AuthState>(...);
// CartStore.ts
export const useCartStore = create<CartState>(...);
// Component.tsx
import { useAuthStore, useCartStore } from './stores';
function Header() {
const user = useAuthStore(state => state.user);
const cartItems = useCartStore(state => state.items);
// ...
}
Типизация с TypeScript: Полный Контроль
Сильная сторона Zustand – первоклассная поддержка TypeScript. Определите интерфейс хранилища при создании (create<MyState>(...)
), и все селекторы, функции обновления будут строго типизированы, обеспечивая безопасность типов до конца цепочки разработки.
Реальный Кейс: Асинхронные Запросы и Формы
Zustand гармонично работает с асинхронной логикой и формами. Рассмотрим загрузку данных:
interface UserStore {
users: User[];
isLoading: boolean;
error: string | null;
fetchUsers: () => Promise<void>;
}
const useUserStore = create<UserStore>((set) => ({
users: [],
isLoading: false,
error: null,
fetchUsers: async () => {
set({ isLoading: true, error: null });
try {
const response = await fetch('/api/users');
const data = await response.json();
set({ users: data, isLoading: false });
} catch (err) {
set({ error: 'Ошибка загрузки', isLoading: false });
}
},
}));
Когда Выбирать Zustand, а Когда Нет?
За:
— Высокая производительность с селекторами.
— Минимальный boilerplate и простая кривая обучения.
— Гибкость миграции: подходит как для мелких утилит, так и крупных приложений.
— Отличная TypeScript-поддержка.
— Легкая интеграция DevTools и persistence (например, zustand-persist
для хранения в localStorage).
Против:
— Для сложных отношений между состояниями с общей бизнес-логикой Redux + Redux Toolkit могут быть более структурированы, особенно в больших командах.
— Если приложение уже глубоко интегрировано с другим стейт-менеджером, миграция может не стоить усилий ради выгоды.
Не Техническое, но Важное
Zustand привлекает склонностью к практичности. Отсутствие шаблонной структуры (нет обязательных reducers
, actions
) дает свободу с опасным оттенком: плохо продуманная организация сторов у новичков может вызвать беспорядок. Архитектурный совет: Сразу разделяйте сторы по предметным областям и используйте middleware для агрегирования кросс-резачинных задач.
Заключительная Реальность
Технологии фронтенда стремительно меняются, но потребность в эффективном и предсказуемом управлении состоянием остается. Zustand не просто легковесная замена—это свежий подход, который удаляет лишнее, фокусируясь на производительности через селекторы, React-подобный API и непринужденный DX. Попробуйте интегрировать его в следующий проект – ощущение легкости и контроля может изменить ваше восприятие управления состоянием. Знание множества инструментов—сила разработчика; Zustand заслуживает место в списке обязательных.