Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Библиотеки состояния

Для большинства задач хватает 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);

Выбирайте селектором ровно те данные, которые нужны компоненту.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.