Локальное vs глобальное состояние

Локальное состояние живёт внутри компонента и недоступно снаружи; глобальное — доступно любому компоненту приложения через контекст или внешнее хранилище.

Зачем нужно

Одна из частых архитектурных ошибок — вынесение всего в глобальный store (Redux, Zustand) без необходимости. Это усложняет код, создаёт лишние зависимости и затрудняет переиспользование компонентов. Правило простое: начинайте с локального state, поднимайте выше только когда данные нужны нескольким компонентам, и выносите в глобальный store только по-настоящему глобальные данные.

Где используется

  • Локальное: открыт/закрыт аккордеон, текущее значение формы, hover-состояние
  • Поднятое (lifted): данные, которые нужны двум соседним компонентам
  • Контекст: тема, язык, данные авторизованного пользователя
  • Глобальный store (Redux/Zustand): корзина покупок, сложная серверная коллекция, состояние с undo/redo

Иерархия состояний

Глобальный store (Redux/Zustand/Jotai)
├── Данные, нужные всему приложению
├── Сложная логика с side effects
└── Состояние, требующее middleware/DevTools

Context API
├── Тема оформления
├── Текущий пользователь
└── Локализация

Lifted State (поднятое состояние)
├── Данные, нужные двум соседним компонентам
└── Хранится в ближайшем общем родителе

Local State (useState/useReducer)
├── UI-состояние компонента (открыт/закрыт)
├── Значения формы
└── Данные, не нужные другим компонентам

Когда использовать что

// ЛОКАЛЬНОЕ — правильно: состояние принадлежит компоненту
function Accordion({ title, children }) {
  const [isOpen, setIsOpen] = useState(false); // только этот компонент знает

  return (
    <div>
      <button onClick={ => setIsOpen(prev => !prev)}>{title}</button>
      {isOpen && <div>{children}</div>}
    </div>
  );
}

// ПОДНЯТОЕ — правильно: данные нужны двум компонентам
function SearchPage() {
  const [query, setQuery] = useState(''); // поднято в общего родителя

  return (
    <>
      <SearchInput value={query} onChange={setQuery} />
      <SearchResults query={query} /> {/* нужен тот же query */}
    </>
  );
}

// ГЛОБАЛЬНОЕ (Context) — правильно: тема нужна везде
const ThemeContext = createContext('light');

// ГЛОБАЛЬНОЕ (Zustand) — правильно: корзина доступна с любой страницы
const useCartStore = create((set, get) => ({
  items: ,
  addItem: (item) => set(state => ({ items: [...state.items, item] })),
  total:  => get.items.reduce((sum, item) => sum + item.price, 0),
}));

Признаки неправильного выбора

Слишком много в store:
  ✗ Redux хранит значение инпута поиска
  ✗ Redux хранит isModalOpen
  → Перенести в useState

Слишком много в local state:
  ✗ useState в компоненте А, useState в компоненте Б — дубль одних данных
  ✗ Постоянный props drilling через 5 уровней
  → Поднять state или использовать Context

Частые ошибки

  • Глобализация всего — помещают каждый флаг в Redux/Zustand; от этого страдает производительность (лишние ре-рендеры) и читаемость.
  • Локализация того, что должно быть глобальным — корзина в useState одного компонента; при переходе на другую страницу данные теряются.
  • Context для часто меняющихся данных — позиция курсора в Context вызывает ре-рендер всех подписчиков при каждом движении мыши.

Связанные темы

Ресурсы