Как 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/типа.