Эволюция управления состоянием в React: от Props Drilling до Zustand

3 мин

Введение

«Как элегантно управлять состоянием?» Это ключевой вопрос, с которым по мере роста проекта сталкивается каждый React-разработчик. От простого состояния внутри компонента до сложного глобального состояния, общего для многих компонентов, — сообщество React изучило множество решений. Их эволюция также отражает то, как углублялось наше понимание компонентной разработки.

Первый этап: простая эпоха — useState и Props Drilling

Вначале в нашем распоряжении был только useState (или this.state в классовых компонентах). Когда одно состояние требовалось нескольким компонентам, официальной рекомендацией React было «поднятие состояния» (Lifting State Up). Состояние переносили к ближайшему общему родительскому компоненту, а затем передавали его и функцию обновления вниз через props.

При глубокой иерархии компонентов этот подход приводил к «протаскиванию props» (Props Drilling): некоторые промежуточные компоненты получали props лишь для передачи их более глубоким потомкам, не используя эти данные сами. Это усиливало связанность компонентов и усложняло рефакторинг и сопровождение.

Второй этап: официальный ответ — Context API

Для решения проблемы Props Drilling React официально предоставил Context API. Он позволяет создать «контекст», предоставить (Provide) значение на верхнем уровне дерева компонентов, а затем напрямую использовать (Consume) это значение в дочернем компоненте на любой глубине без ручной передачи через каждый уровень.

// 1. 创建 Context
const ThemeContext = React.createContext('light');

// 2. 在顶层提供值
<ThemeContext.Provider value="dark">
  <App />
</ThemeContext.Provider>

// 3. 在子组件中消费
const theme = useContext(ThemeContext); // 'dark'

Однако у Context API есть «ловушка»: проблемы с производительностью. При любом изменении Provider, а именно его value, повторно рендерятся все компоненты, использующие этот Context, даже если им нужна лишь небольшая часть объекта value. Поэтому Context API не подходит для управления сложным глобальным состоянием, которое часто меняется.

Третий этап: эпоха объединения — Redux

Когда Context API ещё не достиг зрелости, стремительно появился Redux и быстро стал фактическим стандартом управления состоянием в больших и сложных приложениях. Заимствуя идеи архитектуры Flux и функционального программирования, он принёс следующие принципы:

  • Единственный источник истины (Single Source of Truth): State всего приложения хранится в едином store.
  • State доступен только для чтения: Единственный способ изменить state — выполнить dispatch для action.
  • Изменения выполняются чистыми функциями: Reducer получает предыдущий state и action, а возвращает новый state.

Предсказуемость, мощные средства отладки (путешествия во времени) и богатая экосистема промежуточного ПО позволили Redux решить задачи управления состоянием в больших приложениях. Но его недостаток был столь же очевиден: громоздкий шаблонный код (Boilerplate). Для реализации простой функции разработчикам приходилось писать Actions, Reducers и Dispatchers, что создавало немалую когнитивную нагрузку.

Четвёртый этап: возрождение — лёгкие решения в стиле Hooks-First

С распространением React Hooks и переосмыслением сложности Redux в сообществе стало появляться новое поколение более лёгких библиотек управления состоянием. Их объединяли лаконичные API, простые ментальные модели и полноценное использование возможностей Hooks.

Zustand — один из ярких представителей. Он предоставляет чрезвычайно простую функцию create для создания store, а также:

  • Не требует Provider: Store существует вне дерева компонентов React, поэтому его можно импортировать и использовать где угодно.
  • Минимальный API: Для доступа к состоянию и его обновления достаточно одного Hook.
  • Избирательные подписки и лучшая производительность: Компонент может подписаться только на нужную ему часть state, избегая проблем производительности Context API.
const useStore = create(set => ({
  count: 0,
  inc: () => set(state => ({ count: state.count + 1 })),
}));

function Counter() {
  // 只订阅 count 的变化
  const count = useStore(state => state.count);
  return <h1>{count}</h1>;
}

Заключение: серебряной пули нет — выбирайте по потребностям

История развития управления состоянием в React показывает, что универсального «лучшего решения» не существует: есть лишь решение, которое лучше всего подходит для текущей ситуации.

  • useState: Всегда первый выбор для локального состояния компонента.
  • Context API: Подходит для глобальных данных, которые меняются нечасто, например темы или сведений об аутентификации пользователя.
  • Zustand / Jotai: Для большинства приложений, которым требуется глобальное клиентское состояние, они обеспечивают превосходный баланс между производительностью и удобством разработки.
  • Redux (Redux Toolkit): По-прежнему надёжный выбор для сверхкрупных приложений, которым нужны строгие правила потока данных, сложное промежуточное ПО и мощные возможности отладки.