Управление состоянием в React — один из самых важных моментов при разработке приложений. Многие начинают с useState и useReducer, но со временем понимают, что для глобального состояния нужно что‑то более мощное. Здесь хорошо подойдут Redux, MobX, Recoil и, конечно, Zustand.
Zustand (читается «цуштанд», в переводе с немецкого — «состояние») — это простая и мощная библиотека для управления состоянием в React, которая решает проблемы существующих решений:
Нет бойлерплейта — минимальный код по сравнению с Redux.
Нет контекста — Zustand не требует
Context.Provider, избавляя от лишних ререндеров.Высокая производительность — ререндер происходит только при изменении подписанных частей состояния.
Гибкость — можно использовать Zustand как для глобального состояния, так и для локального.
Создание базового хранилища
В Zustand хранилище — это обычный хук, который возвращает состояние и экшены для его изменения. Реализуем счетчик.
Создадим простое хранилище с count и экшенами increment и reset:
import { create } from 'zustand'; // создаём хранилище Zustand const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), reset: () => set({ count: 0 }), }));
set— функция для изменения состояния.state.count + 1— обновляем состояние на основе предыдущего.reset— сбрасывает счетчик в0.
Теперь подключим Zustand в React‑компонент:
function Counter() { const { count, increment, reset } = useStore(); return ( <div> <h1>{count}</h1> <button onClick={increment}>+1</button> <button onClick={reset}>Сброс</button> </div> ); }
Zustand не требует ни useReducer, ни контекста, ни Redux — все понятно.
Как избежать лишних ререндеров?
Одна из главных фич Zustand — селекторы, позволяющие подписываться на отдельные части состояния.
Если использовать useStore() без параметров, компонент будет перерисовываться при любом изменении хранилища.
function BadComponent() { const store = useStore(); // будет ререндериться на любое изменение со��тояния return <p>{store.count}</p>; }
Используем селектор, подписываясь только на count:
function GoodComponent() { const count = useStore((state) => state.count); // перерисовывается только при изменении count return <p>{count}</p>; }
Плюс можно использовать useShallow, чтобы подписываться на несколько значений без лишних ререндеров:
import { useShallow } from 'zustand/react/shallow'; const { name, age } = useUserStore( useShallow((state) => ({ name: state.name, age: state.age })) );
Асинхронные действия
Допустим, у нас есть API https://jsonplaceholder.typicode.com/users, из которого хочется хотим загружать пользователей.
Создадим Zustand‑хранилище:
const useUserStore = create((set) => ({ users: [], fetchUsers: async () => { const response = await fetch('https://jsonplaceholder.typicode.com/users'); const data = await response.json(); set({ users: data }); }, }));
fetchUsers делает fetch, получает JSON и обновляет users в хранилище, после этого set({ users: data }) полностью заменяет состояние.
Используем в компоненте:
function UserList() { const { users, fetchUsers } = useUserStore(); useEffect(() => { fetchUsers(); }, []); return ( <ul> {users.map((user) => ( <li key={user.id}>{user.name}</li> ))} </ul> ); }
Теперь при монтировании компонент загружает пользователей и обновляет состояние.
Zustand без React
Zustand позволяет работать вне React, например, для WebSockets. Например:
import { createStore } from 'zustand/vanilla'; const store = createStore((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })); // Доступ к состоянию store.getState().count; store.setState({ count: 10 });
Middleware в Zustand
Zustand поддерживает middleware.
persist — сохранение состояния в
localStorageimmer — удобная работа с вложенными объектами
devtools — интеграция с Redux DevTools
persist
Чтобы сохранить состояние между перезапусками браузера:
import { persist } from 'zustand/middleware'; const useStore = create( persist( (set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), }), { name: 'counter-storage' } ) );
Теперь count сохраняется в localStorage.
immer
Если нужно обновлять вложенные объекты без лишних мутаций:
import { immer } from 'zustand/middleware/immer'; const useStore = create( immer((set) => ({ user: { name: 'Alice', age: 25 }, updateName: (name) => set((state) => { state.user.name = name; }), })) );
Redux DevTools
Zustand интегрируется с Redux DevTools, позволяя отслеживать изменения:
import { devtools } from 'zustand/middleware'; const useStore = create( devtools((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), })) );
Разделение хранилища на слайсы
Для крупных приложений состояние можно разделять на слайсы:
const createUserSlice = (set) => ({ user: { name: 'Alice', age: 25 }, updateUser: (newUser) => set({ user: newUser }), }); const createAuthSlice = (set) => ({ isLoggedIn: false, login: () => set({ isLoggedIn: true }), logout: () => set({ isLoggedIn: false }), }); const useStore = create((...a) => ({ ...createUserSlice(...a), ...createAuthSlice(...a), }));
Подробнее с zustand можно ознакомиться здесь.
Не могу не упомянуть об открытых уроках по React, которые пройдут скоро в Otus:
6 февраля: «Что нового в React 19?». На уроке вас ждет обзор новых хуков и возможностей создания новых пользовательских элементов. Записаться
20 февраля: «React и графические библиотеки: визуализация данных». Узнаете, как визуализировать данные в React и интегрировать визуализации в существующие приложения. Записаться
