← Назад к вопросам

Как работает под капотом useState?

2.0 Middle🔥 272 комментариев
#JavaScript Core

Комментарии (2)

🐱
deepseek-v3.2PrepBro AI4 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Как работает useState под капотом?

useState — это один из самых фундаментальных хуков (hooks) в React, позволяющий компонентам управлять своим внутренним состоянием. Чтобы понять его работу "под капотом", нужно разобраться в нескольких ключевых концепциях: диспетчер хуков (Hook Dispatcher), фибры (Fiber nodes), схема очередей (queueing mechanism) и принцип постоянства ссылок (persistent references).

Основная схема работы

Когда вы вызываете useState(initialState) в функциональном компоненте, React выполняет следующие действия:

  1. Определение диспетчера хуков. Во время рендера React использует специальный внутренний объект — диспетчер хуков. В зависимости от этапа рендера (первичный или повторный) этот диспетчер меняет свою реализацию. Для useState на первичном рендере диспетчер создает состояние и связывает его с фибром компонента, а на повторном — возвращает текущее значение и предоставляет функцию обновления.

  2. Связь с фибром компонента. Каждый компонент в React представлен внутренней структурой данных — фибром (Fiber node). Fiber содержит всю метаинформацию о компоненте: его тип, пропсы, состояние, ссылки на DOM-узлы и, что важно, список хуков. Когда вызывается useState, React:

    *   Создает объект, представляющий состояние.
    *   Добавляет этот объект в список хуков текущего фибра.
    *   Возвращает пользователю текущее значение состояния и функцию для его обновления.

// Пример использования useState
function Counter() {
  const [count, setCount] = useState(0); // initialState = 0

  // Под капотом React примерно делает следующее:
  // 1. Проверяет, есть ли уже hook object для этого вызова в Fiber
  // 2. Если нет (первый рендер) — создает его со значением 0
  // 3. Возвращает массив [currentState, dispatchFunction]
  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

Структура объекта хука состояния

Внутренний объект хука для состояния обычно имеет следующую структуру:

// Упрощенная внутренняя структура (не реальный код React)
const hook = {
  memoizedState: 0,       // текущее значение состояния (count)
  baseState: 0,          // базовое состояние для вычислений
  queue: {               // очередь обновлений
    pending: null,       // последнее добавленное обновление
    dispatch: null,      // функция dispatch, связанная с setCount
  },
  next: null,            // ссылка на следующий хук в списке (если есть useState(2), useState('text'))
};

Механизм обновления состояния: очередь и ре-рендер

Ключевой момент — функция setCount (или любая функция обновления, возвращаемая useState) не изменяет состояние напрямую. Она:

  1. Создает обновление (update object) и добавляет его в очередь (queue) хука.
  2. Планирует ре-рендер компонента. React помечает фибр компонента как нуждающийся в повторном рендере и добавляет его в очередь ре-рендера (вместе с другими обновлениями, например, от другого useState или родительского компонента).
  3. Выполняет повторный рендер. Когда React начинает повторный рендер компонента:
    *   Он снова вызывает функцию компонента.
    *   `useState` теперь работает в режиме **повторного рендера**: диспетчер хуков берет объект хука из фибра, **обрабатывает все обновления из его очереди** (применяя их последовательно к базовому состоянию) и вычисляет новое значение `memoizedState`.
    *   Возвращает это новое значение и ту же функцию обновления (постоянную ссылку).

// Упрощенная логика обработки очереди обновлений во время рендера
function processStateQueue(hook) {
  let newState = hook.baseState;
  let update = hook.queue.pending;
  while (update) {
    // Обновление может быть числом или функцией (setCount(prev => prev + 1))
    newState = typeof update.action === 'function'
      ? update.action(newState)
      : update.action;
    update = update.next;
  }
  hook.memoizedState = newState;
  hook.baseState = newState;
  hook.queue.pending = null;
  return newState;
}

Важные особенности и оптимизации

  • Постоянство ссылок на функцию обновления. Функция setCount, которую вы получаете при первом рендере, одна и та же на протяжении всей жизни компонента. Это позволяет безопасно использовать ее в useEffect, useCallback без необходимости добавлять в зависимости.
  • Батчинг (batch processing) обновлений. В синхронном коде (например, несколько вызовов setCount внутри одного события) React батчирует (группирует) обновления. Все обновления помещаются в очередь, но ре-рендер будет выполнен только один раз после завершения события. Однако в асинхронных контекстах (например, внутри setTimeout) батчинг может не работать, и каждый вызов может вызывать отдельный ре-рендер (в React 17 и ниже; в React 18 автоматический батчинг расширен).
  • Правило "хуки вызываются на верхнем уровне". React связывает хуки с фибром в порядке их вызова. Именно поэтому хуки нельзя вызывать условно или в циклах — порядок должен быть стабильным между рендерами, чтобы React мог корректно сопоставить объекты хуков из предыдущего рендера с текущими вызовами.

Сравнение с классовыми компонентами

В отличие от this.setState в классовых компонентах, который может частично обновлять состояние и принимает второй аргумент-колбэк, useState:

  • Заменяет состояние полностью, а не сливает его (хотя можно имитировать слияние через спред-оператор для объектов).
  • Не имеет колбэка завершения обновления. Для выполнения действий после обновления нужно использовать useEffect.

Взаимодействие с concurrent features (React 18)

В React 18 с concurrent режимами (например, startTransition) внутренняя обработка useState стала еще сложнее. Обновления могут иметь разные приоритеты, и React может откладывать или прерывать рендеры, чтобы обеспечить более плавный пользовательский интерфейс. Очередь обновлений теперь учитывает эти приоритеты.

Итог: useState — это не просто функция, сохраняющая значение. Это сложный механизм, интегрированный в архитектуру фибров и диспетчер хуков React, обеспечивающий управление состоянием с очередью обновлений, батчингом и гарантией стабильного порядка. Его работа обеспечивает декларативность и эффективность обновлений интерфейса в ответ на изменения данных.