Библиотеки состояния
Для большинства задач хватает useState, useReducer и контекста. Но когда общие данные нужны десяткам компонентов в разных частях приложения и часто меняются, контекст становится неудобным: любое изменение перерисовывает всех, кто его читает. Тогда используют библиотеку состояния — внешнее хранилище (store), из которого компонент берёт только нужную ему часть.
Популярные библиотеки
| Библиотека | Особенность |
|---|---|
| Zustand | Минимум кода, хранилище — один хук |
| Redux Toolkit | Строгая структура, редьюсеры и действия, много инструментов |
| Jotai | Состояние из мелких независимых частей — атомов |
| MobX | Изменяемые объекты, которые сами сообщают об изменениях |
Начать проще всего с Zustand: он маленький и похож на уже знакомые хуки.
Установка
npm install zustandХранилище
Сделаем в блоге избранные статьи, которые видны в меню и в списке. Создайте src/store/useFavorites.js:
// src/store/useFavorites.js
import { create } from 'zustand';
export const useFavorites = create((set) => ({
ids: [],
toggle: (id) =>
set((state) => ({
ids: state.ids.includes(id)
? state.ids.filter((item) => item !== id)
: [...state.ids, id],
})),
}));create получает функцию, которая возвращает начальные данные и функции для их изменения. set заменяет переданные поля, а остальные оставляет как есть. Правило то же, что в React: массив не меняют на месте, а создают новый.
Использование в компонентах
Хранилище — это хук. Компонент передаёт ему функцию-селектор, которая выбирает нужную часть:
// src/components/FavoriteButton.jsx
import { useFavorites } from '../store/useFavorites.js';
export default function FavoriteButton({ postId }) {
const isFavorite = useFavorites((state) => state.ids.includes(postId));
const toggle = useFavorites((state) => state.toggle);
return <button onClick={() => toggle(postId)}>{isFavorite ? '★' : '☆'}</button>;
}// src/components/FavoritesCount.jsx
import { useFavorites } from '../store/useFavorites.js';
export default function FavoritesCount() {
const count = useFavorites((state) => state.ids.length);
return <span>Избранное: {count}</span>;
}Выведите <FavoritesCount /> в меню Nav, а <FavoriteButton postId={post.id} /> — рядом с каждым заголовком в списке статей. Щелчок по звёздочке закрашивает её, а счётчик в меню растёт. Обёртка-провайдер, как у контекста, не нужна: хранилище доступно из любого компонента.
Перерисовка только нужного
Компонент перерисовывается, только когда меняется значение, которое вернул его селектор. Счётчик FavoritesCount обновится при добавлении любой статьи, а звёздочка — только у той статьи, которую нажали.
Когда это нужно
Не добавляйте библиотеку заранее. Сначала держите состояние в компонентах и поднимайте его по мере надобности. Если пропсы приходится протаскивать через много уровней, а контекст перерисовывает слишком многое, — пора подключить хранилище.
Частая ошибка
Вызов хука без селектора подписывает компонент на всё хранилище:
// Неправильно: компонент перерисуется при любом изменении в хранилище.
const { ids } = useFavorites();
// Правильно: только при изменении ids.
const ids = useFavorites((state) => state.ids);Выбирайте селектором ровно те данные, которые нужны компоненту.