Правила хуков и useRef

Запоминаем два железных правила хуков и знакомимся с useRef — «карманом» для значений вне состояния.

Правила хуков — два ограничения, гарантирующие, что React правильно связывает вызовы хуков с компонентом между перерисовками.

Правило 1: только на верхнем уровне

Хуки нельзя вызывать внутри условий, циклов и вложенных функций — только на верхнем уровне компонента. React опирается на порядок вызовов хуков, и этот порядок должен быть одинаковым при каждой отрисовке.

// ❌ Хук внутри условия — порядок «плавает»
function Bad({ show }) {
  if (show) {
    const [x, setX] = useState(0); // нельзя!
  }
}

// ✅ Хук на верхнем уровне, условие — внутри
function Good({ show }) {
  const [x, setX] = useState(0);
  if (show) { /* используем x */ }
}

Почему так

React не знает имён ваших переменных. Он сопоставляет состояния по порядку: «первый useState, второй useState...». Если на одной отрисовке хук вызван, а на другой — пропущен (из-за условия), порядок собьётся, и React перепутает состояния. Отсюда и правило.

Правило 2: только из React-функций

Хуки вызывают только из функциональных компонентов и из других хуков (кастомных). Из обычных функций — нельзя.

function helper() {
  const [x] = useState(0); // ❌ это обычная функция, не компонент
}

useRef: значение без перерисовки

Иногда нужно сохранить значение между перерисовками, но без запуска перерисовки при его изменении. Для этого есть useRef — он создаёт объект с полем .current:

function StopWatch() {
  const intervalRef = useRef(null); // переживёт перерисовки

  function start() {
    intervalRef.current = setInterval(() => {
      console.log("тик");
    }, 1000);
  }

  function stop() {
    clearInterval(intervalRef.current);
  }
  // ...
}

Изменение intervalRef.current не вызывает перерисовку — поэтому ref удобен для таймеров, предыдущих значений, флагов.

useRef для доступа к DOM

Главное применение — прямая ссылка на DOM-элемент, например чтобы поставить фокус:

function SearchField() {
  const inputRef = useRef(null);

  useEffect(() => {
    inputRef.current.focus(); // фокус при появлении
  }, []);

  return <input ref={inputRef} />;
}

Проп ref={inputRef} связывает inputRef.current с реальным DOM-узлом после отрисовки.

state vs ref

useStateuseRef
Перерисовка при измененииданет
Для чегоданные, влияющие на UIDOM-узлы, таймеры, «закулисные» значения

Итог

  • Хуки вызывают только на верхнем уровне (не в условиях/циклах) и только из компонентов или кастомных хуков.
  • React сопоставляет хуки по порядку вызова — потому порядок должен быть стабилен.
  • useRef хранит значение между рендерами без перерисовки и даёт ссылку на DOM через проп ref.
Проверьте себя
1. Где разрешено вызывать хуки?
AВ любом месте кода
BТолько на верхнем уровне компонента или кастомного хука
CВнутри условий и циклов
DТолько в обработчиках событий
2. Почему нельзя вызывать хук внутри условия if?
AReact не поддерживает if
BReact сопоставляет состояния по порядку вызова хуков, а условие нарушает этот порядок
CЭто замедляет рендер
Dif несовместим с JSX
3. Чем useRef отличается от useState?
AuseRef всегда вызывает перерисовку
BИзменение useRef НЕ вызывает перерисовку; он хранит значение между рендерами и даёт ссылку на DOM
CuseRef нельзя использовать с DOM
DЭто одно и то же
Поддержать проект