Почему в React-компонентах нельзя вызывать хуки внутри условий if или циклов for (разбор механизма очереди хуков)?
React-хуки (useState, useEffect, useRef и другие) работают на основе внутреннего механизма, который принято называть «очередью хуков» (hooks queue). Чтобы понять, почему хуки нельзя вызывать внутри условий if или циклов for, нужно разобраться, как React отслеживает состояние каждого хука.
## Как React хранит состояние хуков
Каждый компонент в React связан с так называемым «волокном» (fiber) — внутренним объектом, который хранит всю информацию о компоненте. Внутри этого объекта есть связный список (linked list), где каждый узел соответствует одному вызову хука. При первом рендере React последовательно создаёт узлы в этом списке: первый вызов useState создаёт первый узел, второй вызов useEffect — второй узел, и так далее.
При каждом последующем рендере React не создаёт новый список, а проходит по уже существующему, сопоставляя каждый вызов хука с соответствующим узлом по его порядковому номеру. Именно поэтому порядок вызовов хуков должен быть строго одинаковым при каждом рендере.
## Что происходит при нарушении порядка
Представим такой код:
javascript
function MyComponent({ isLoggedIn }) {
const [name, setName] = useState(»);
if (isLoggedIn) {
const [age, setAge] = useState(0); // Нарушение!
}
const [email, setEmail] = useState(»);
}
При первом рендере, если isLoggedIn === true, React создаст три узла в очереди:
— Узел 1 → name
— Узел 2 → age
— Узел 3 → email
При следующем рендере, если isLoggedIn === false, React попытается сопоставить:
— Узел 1 → name (корректно)
— Узел 2 → email (ошибка! React думает, что это age)
В результате email получит значение, предназначенное для age, и всё состояние компонента «съедет». Это приводит к трудноотлавливаемым багам.
## Почему циклы тоже запрещены
С циклами ситуация аналогична: количество итераций может меняться от рендера к рендеру, а значит, количество вызовов хуков будет разным. React не сможет корректно сопоставить узлы очереди с вызовами.
## Как правильно решать такие задачи
1. **Условная логика** — переносите условие внутрь хука, а не сам хук под условие:
javascript
useEffect(() => {
if (isLoggedIn) {
fetchUserData();
}
}, [isLoggedIn]);
2. **Работа со списками** — если нужно несколько однотипных состояний, используйте массив или объект в одном useState:
javascript
const [items, setItems] = useState([]);
3. **Вынос в отдельные компоненты** — если логика сложная, разбейте компонент на части, каждая из которых имеет стабильный набор хуков.
## ESLint-плагин как защита
Для предотвращения подобных ошибок команда React разработала официальный ESLint-плагин `eslint-plugin-react-hooks` с правилом `rules-of-hooks`, которое статически анализирует код и предупреждает о нарушениях ещё на этапе написания кода.
Понимание механизма очереди хуков помогает не только избегать ошибок, но и писать более предсказуемый и поддерживаемый React-код.
