PassFront
  1. PassFront
  2. База знаний
  3. Хуки

Как hook useState сохраняет состояние между перерендерами?

Хуки

Функция компонента «умирает» после каждого вызова — состояние переживает рендеры, потому что живёт не в функции, а в React: в списке хуков на fiber-узле компонента.

Механика по шагам

Монтирование. Для компонента создаётся fiber (внутренний объект React). Первый вызов useState(initial):

  • создаёт ячейку состояния (hook-объект) и записывает туда initial;
  • ячейка добавляется в связный список хуков этого fiber'а;
  • возвращается [значение, сеттер], где сеттер «знает» свой fiber и свою ячейку.

Ререндер. Функция вызывается заново, все локальные переменные — новые. Но React в этот момент знает, какой fiber рендерится, и ведёт курсор по списку хуков: первый вызов useState читает первую ячейку, второй — вторую... Инициализатор игнорируется — возвращается сохранённое значение.

Обновление. setCount(5) кладёт обновление в очередь своей ячейки и планирует ререндер fiber'а; при следующем вызове useState вернёт применённое значение.

Почему порядок вызовов священен

Сопоставление «вызов ↔ ячейка» — позиционное, никаких имён у хуков нет:

if (cond) {
  const [a] = useState(1); // ❌ сдвинет все последующие хуки
}

Условный хук смещает курсор — состояния «переезжают» в чужие вызовы. Отсюда правило хуков: только верхний уровень, стабильный порядок.

Следствия

  • Состояние привязано к экземпляру в позиции дерева: тот же компонент в том же месте — то же состояние; смена типа/key — новый fiber, чистый state.
  • Два экземпляра компонента — два fiber'а, независимые состояния.

Коротко для собеседования

useState хранит значение в ячейке связного списка хуков на fiber-узле компонента; при ререндере вызовы useState по порядку читают свои ячейки (инициализатор игнорируется), сеттер пишет в свою ячейку и планирует рендер. Позиционное сопоставление объясняет правило «хуки только на верхнем уровне» и сброс state при смене key/типа.

Другие вопросы раздела