Как работает useRef в React?

MiddleReact · Frontend·Обновлено 30 июня 2026
Коротко
useRef возвращает мутабельный объект с полем current, который сохраняет своё значение между рендерами и не вызывает повторный рендер при изменении.

Что такое useRef

useRef — это хук React, который возвращает мутабельный объект { current: initialValue }. Этот объект живёт на протяжении всего жизненного цикла компонента и сохраняет своё значение между рендерами.

Главное отличие от useState: изменение ref.current не вызывает повторный рендер компонента.

Два основных сценария использования

1. Доступ к DOM-элементу

Самый распространённый случай — получить ссылку на реальный DOM-узел, чтобы вызвать его методы напрямую.

import { useRef, useEffect } from 'react';

function FocusInput() {
  // Создаём ref с начальным значением null
  const inputRef = useRef<HTMLInputElement>(null);

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

  return <input ref={inputRef} type="text" />;
}

React сам устанавливает inputRef.current в DOM-элемент при монтировании и обнуляет при размонтировании.

2. Хранение мутабельного значения без перерендера

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

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

function Timer() {
  const [count, setCount] = useState(0);
  // Храним id интервала — менять его незачем в UI
  const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null);

  useEffect(() => {
    intervalRef.current = setInterval(() => {
      setCount(c => c + 1);
    }, 1000);

    return () => {
      if (intervalRef.current) clearInterval(intervalRef.current);
    };
  }, []);

  return <p>Прошло секунд: {count}</p>;
}

Как useRef работает под капотом

При каждом рендере React возвращает один и тот же объект. Объект не пересоздаётся — это принципиально отличает useRef от создания объекта внутри компонента напрямую (тот создавался бы заново на каждом рендере).

function Example() {
  // Этот объект создаётся заново при каждом рендере — НЕВЕРНО для хранения
  const badRef = { current: 0 };

  // Этот объект стабилен между рендерами — ВЕРНО
  const goodRef = useRef(0);
}

Типичные паттерны

Хранение предыдущего значения

function usePrevious<T>(value: T): T | undefined {
  const prevRef = useRef<T>();

  useEffect(() => {
    // Обновляем после рендера, поэтому в текущем рендере prevRef.current — старое значение
    prevRef.current = value;
  });

  return prevRef.current;
}

Избежание stale closure

function SearchComponent({ onSearch }: { onSearch: (q: string) => void }) {
  const onSearchRef = useRef(onSearch);

  useEffect(() => {
    // Обновляем ref при каждом рендере, не добавляя в deps эффекта
    onSearchRef.current = onSearch;
  });

  useEffect(() => {
    const handler = () => onSearchRef.current('query');
    window.addEventListener('keydown', handler);
    return () => window.removeEventListener('keydown', handler);
  }, []); // deps пустые — ref всегда актуален
}

Важные ограничения

  • Не читайте и не пишите ref.current во время рендера (исключение — ленивая инициализация). Это нарушает чистоту функции рендера.
  • Для передачи ref в дочерний компонент используйте forwardRef (или ref как обычный пропс в React 19+).
  • useRef не реактивен: если вам нужна реакция UI на изменение значения — используйте useState.

Что хочет услышать интервьюер

Понимание разницы между useRef и useState: изменение ref не вызывает рендер

Знание двух основных сценариев: доступ к DOM и хранение мутабельного значения между рендерами

Понимание того, что React возвращает один и тот же объект на каждом рендере

Умение объяснить проблему stale closure и как useRef помогает её решить

Знание ограничений: не читать current во время рендера, forwardRef для дочерних компонентов

Пример: Доступ к DOM-элементу

import { useRef, useEffect } from 'react';

function AutoFocusInput() {
  const inputRef = useRef<HTMLInputElement>(null);

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

  return <input ref={inputRef} placeholder="Введите текст" />;
}

Пример: Хранение значения без перерендера

import { useRef, useState } from 'react';

function RenderCounter() {
  const [value, setValue] = useState('');
  // Счётчик рендеров — не нужен в UI, не должен триггерить рендер
  const renderCount = useRef(0);
  renderCount.current += 1;

  return (
    <div>
      <input value={value} onChange={e => setValue(e.target.value)} />
      <p>Количество рендеров: {renderCount.current}</p>
    </div>
  );
}

Пример: Паттерн usePrevious

import { useRef, useEffect } from 'react';

function usePrevious<T>(value: T): T | undefined {
  const ref = useRef<T>();

  useEffect(() => {
    // Сохраняем текущее значение ПОСЛЕ рендера
    // Поэтому во время рендера ref.current содержит предыдущее значение
    ref.current = value;
  });

  return ref.current;
}

// Использование
function Counter() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);

  return (
    <p>Текущее: {count}, предыдущее: {prevCount}</p>
  );
}

Типичные ошибки

Путают useRef с useState и ожидают перерендера при изменении ref.current

Читают или пишут ref.current прямо во время рендера вместо useEffect

Используют useRef для хранения данных, которые должны отображаться в UI — вместо useState

Забывают про forwardRef при передаче ref в дочерний компонент (до React 19)

Создают объект { current: value } вручную внутри компонента, не понимая, что он будет пересоздаваться на каждом рендере

Лучшие курсы по теме

изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Feature-Sliced Design

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.5
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее