Kodokon kodokon.com

useRef и работа с DOM

Работай с DOM и храни значения экземпляра через useRef, не нарушая чистоту рендера.

7 мин · 3 вопросов

Открыть этот урок в Kodokon

useRef возвращает изменяемую коробку - объект с единственным свойством current - идентичность которой переживает любой рендер. Два принципиальных отличия от состояния: изменение ref.current не вызывает перерисовку, а сама правка видна сразу, без ожидания следующего рендера. Отсюда две семьи применений: ссылка на DOM-узел и хранение значения экземпляра, которое должно пережить рендеры, не управляя отображением.

JSX
import { useEffect, useRef } from "react";

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

  useEffect(() => {
    inputRef.current.focus();
  }, []);

  return <input ref={inputRef} type="search" />;
}
React привязывает узел к рефу после того, как рендер зафиксирован.

Со стороны значений экземпляра в реф попадает всё, что должно сохраняться, не вызывая рендера: идентификатор, который возвращает setInterval, экземпляр внешней библиотеки (карта, редактор, видеоплеер) или значение из предыдущего рендера. Золотое правило остаётся прежним: рендер должен быть чистой функцией от пропсов и состояния. Рефы читают и записывают в эффектах и обработчиках событий, а не во время рендера.

JAVASCRIPT
import { useEffect, useRef } from "react";

function usePrevious(value) {
  const ref = useRef(undefined);

  useEffect(() => {
    ref.current = value;
  }, [value]);

  return ref.current;
}
Эффект выполняется после рендера: реф остаётся на один рендер позади.

Чтобы отдать DOM-узел наружу из своего компонента, передай реф дальше: начиная с React 19 ref - обычный пропс функциональных компонентов; раньше без forwardRef было не обойтись. И последний рефлекс, который стоит выработать: предпочитай декларативный подход, когда состояния достаточно, чтобы описать результат (CSS-класс, атрибут disabled). Оставь рефы для территории, которую React не покрывает: фокус, выделение текста, измерение элемента, прокрутка, воспроизведение медиа.

JSX
import { useRef } from "react";

function TextField({ ref, ...props }) {
  return <input ref={ref} {...props} />;
}

function Form() {
  const fieldRef = useRef(null);

  return (
    <>
      <TextField ref={fieldRef} />
      <button onClick={() => fieldRef.current.focus()}>
        Give focus
      </button>
    </>
  );
}
Начиная с React 19 реф передаётся как любой другой пропс.

Проверка знаний

Убедись, что запомнил ключевые моменты этого урока.

  1. Какое принципиальное отличие отделяет useRef от useState при хранении значения?
    • Изменение ref.current не вызывает перерисовку, в отличие от обновления состояния
    • useRef сохраняет своё значение после размонтирования компонента
    • useRef может хранить только DOM-узлы
    • Рефы общие для всех экземпляров компонента
  2. Почему обращаться к inputRef.current нужно в useEffect, а не во время рендера?
    • Потому что useEffect группирует обращения к DOM ради производительности
    • Потому что во время рендера рефы заблокированы для чтения
    • Потому что React привязывает узел только после коммита: во время первого рендера current ещё null
  3. Что возвращает usePrevious(value) на самом первом рендере?
    • Текущее значение value
    • undefined, потому что эффект, записывающий в реф, ещё не выполнился
    • null
    • Значение следующего рендера