Назад
Назад к заметкам
· 4 мин чтения

useRef в React: быстрый способ управлять DOM и хранить mutable-данные

Хук useRef позволяет получить доступ к реальному DOM-элементу и хранить mutable-данные без лишних перерисовок. Примеры использования, типичные ошибки и сравнение с useState.

reacthooksuserefperformancestate-management

Если ваш компонент “залипает” из-за частых обновлений, а нужен доступ к конкретному элементу, хук useRef подходящее решение. Он хранит изменяемое значение, которое не участвует в жизненном цикле рендера, и не нарушает однонаправленный поток данных.

Ниже показано, где он действительно полезен, какие ошибки часто встречаются и какие альтернативы доступны. Все на живом примере, без лишних абстракций.


Что хранит useRef и как он отличается от useState

ХукЧто хранитТриггерит перерисовкуКак читается в JSX
useStateСериализуемое значениеДа (при set){state}
useRefЛюбой объект (mutable)Нетref.current

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

  • ссылки на DOM-элемент;
  • таймеров, подписок, предыдущих пропсов;
  • любых внешних mutable-данных.

Если требуется сохранять значение между рендерами, но без запуска нового рендера, выбирайте useRef.

Как привязать ref к DOM-элементу

Самый простой случай получить ссылку на <input> и управлять фокусом из кода.

import { useRef } from "react";

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

  const focusInput = () => {
    // current реальный DOM-элемент
    inputRef.current?.focus();
  };

  return (
    <div>
      <input ref={inputRef} placeholder="Поиск" />
      <button onClick={focusInput}>Фокусировать поле</button>
    </div>
  );
}

ref передается через ref={inputRef}. После монтирования inputRef.current содержит <input> и мы можем вызывать любые методы DOM. Это классический пример применения хука.

Хранение мутируемого значения без перерисовки

Иногда нужно сохранить предыдущее значение пропса для сравнения в useEffect.

import { useEffect, useRef } from "react";

function Counter({ value }) {
  const prev = useRef(value);

  useEffect(() => {
    if (prev.current !== value) {
      console.log("Изменилось значение:", prev.current, "→", value);
    }
    prev.current = value; // обновляем без рендера
  }, [value]);

  return <div>Текущее: {value}</div>;
}

prev.current всегда хранит предыдущее значение, но UI не обновляется. Такой паттерн часто заменяет useState, когда нужно лишь кэшировать данные.

Когда useRef не заменит useState

СценарийПравильный хук
Отображать новое значение в UIuseState
Хранить ссылку на элементuseRef
Передать объект дочернему компоненту без рендераuseRef + React.memo
Интеграция с таймерами, WebSocket и т.п.useRef

Попытка хранить в useRef то, что должно влиять на вывод, приводит к “ловушке” новичков: UI не обновится.

Практический пример: бесконечный скролл списка

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

function InfiniteList() {
  const [items, setItems] = useState(Array.from({ length: 20 }, (_, i) => i));
  const listRef = useRef(null); // контейнер списка

  const loadMore = () => {
    setItems((prev) => [
      ...prev,
      ...Array.from({ length: 20 }, (_, i) => prev.length + i),
    ]);
  };

  useEffect(() => {
    const el = listRef.current;
    if (!el) return;

    const onScroll = () => {
      if (el.scrollHeight - el.scrollTop === el.clientHeight) {
        loadMore();
      }
    };
    el.addEventListener("scroll", onScroll);
    return () => el.removeEventListener("scroll", onScroll);
  }, []);

  return (
    <div
      ref={listRef}
      style={{ height: 200, overflowY: "auto", border: "1px solid #ccc" }}
    >
      {items.map((item) => (
        <div key={item} style={{ padding: "8px" }}>
          Элемент #{item}
        </div>
      ))}
    </div>
  );
}

listRef нужен только для привязки обработчика скролла; изменение scrollTop внутри обработчика не вызывает рендер. Если бы мы хранили позицию в useState, каждый микроскролл приводил бы к лишнему рендеру и тормозам. Это еще один случай, когда хук помогает экономить ресурсы.


FAQ / чеклист

  • Не храните в useRef то, что выводится UI не обновится без setState.
  • Инициализируйте ref (useRef(null)), иначе current будет undefined до монтирования.
  • Для доступа к последнему пропу используйте useRef внутри useEffect.
  • Не путайте ref с forwardRef первый просто хранит значение, второй передает его вниз по иерархии.
  • Если нужен один и тот же ref в нескольких компонентах, вынесите его в кастомный хук или контекст; см. материал о React Context.

Хук пригодится каждый раз, когда нужно “дотянуться” до реального DOM, хранить mutable-данные вне потока рендера или интегрировать сторонние библиотеки без лишних перерисовок. В большинстве UI-задач он экономит ресурсы и упрощает код, позволяя сосредоточиться на бизнес-логике, а не на управлении состоянием.

Готов начать?

Выбери удобный способ связи — напиши напрямую или оставь заявку

Написать в Telegram

Отвечу в течение пары часов