Как работает useRef и где его уместно применять?

useRef — это хук в React, который предоставляет способ создать изменяемую ссылку на значение, которая не вызывает повторный рендер компонента при изменении. Он используется в основном для доступа к DOM-элементам, хранения мутируемых значений между рендерами и работы с таймерами, анимациями и внешними API.

Сигнатура и структура

const ref = useRef(initialValue);

Возвращает объект:

{
current: initialValue
}

Свойство .current можно изменять напрямую. Оно сохраняет своё значение между рендерами, но не вызывает повторный рендер, если его изменить.

Основные сценарии применения

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

Это самый распространённый способ использования. Когда нужно взаимодействовать с нативным DOM-элементом — установить фокус, измерить размеры, прокрутить, применить scrollIntoView и т.д.

import { useRef, useEffect } from 'react';
function InputFocus() {
const inputRef = useRef(null);
useEffect(() => {
inputRef.current.focus(); // Фокусируемся на input при монтировании
}, \[\]);
return <input ref={inputRef} />;
}

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

Иногда нужно сохранить значение между рендерами, не вызывая повторный рендер при его изменении. Например, счётчик, который не влияет на визуальный интерфейс.

function Timer() {
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(() => {
console.log('tick');
}, 1000);
return () => clearInterval(intervalRef.current);
}, \[\]);
return <p>Смотри в консоль</p>;
}

3. Хранение предыдущего значения пропса или стейта

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

function PreviousValue({ value }) {
const prevRef = useRef();
useEffect(() => {
prevRef.current = value;
});
return (
<div>
<p>Текущее: {value}</p>
<p>Предыдущее: {prevRef.current}</p>
</div>
);
}

4. Флаг монтирования

Иногда нужно знать, монтирован ли компонент. Это особенно полезно при работе с асинхронными эффектами, чтобы избежать утечек памяти.

function AsyncComponent() {
const isMounted = useRef(true);
useEffect(() => {
fetch('/api/data')
.then((res) => res.json())
.then((data) => {
if (isMounted.current) {
// Безопасно обновлять состояние
}
});
return () => {
isMounted.current = false;
};
}, \[\]);
}

5. Анимации и requestAnimationFrame

useRef отлично подходит для хранения ссылки на requestAnimationFrame, чтобы отменить его при необходимости.

function Animation() {
const frameRef = useRef();
const animate = () => {
// логика анимации
frameRef.current = requestAnimationFrame(animate);
};
useEffect(() => {
frameRef.current = requestAnimationFrame(animate);
return () => cancelAnimationFrame(frameRef.current);
}, \[\]);
}

Отличия от других хуков

Хук Триггерит ререндер Сохраняется между рендерами Может хранить DOM-элемент
useState ✅ Да ✅ Да ❌ Нет
--- --- --- ---
useRef ❌ Нет ✅ Да ✅ Да
--- --- --- ---
useMemo ❌ Нет ✅ Да ❌ Нет (только значение)
--- --- --- ---

Когда не стоит использовать useRef

  • Когда данные напрямую участвуют в отображении компонента — тогда следует использовать useState.

  • Когда важна синхронная реакция на изменение данных — useEffect + useState лучше подойдут.

  • Не использовать ref.current = ... как костыль для передачи данных между компонентами — это может нарушить логику React.

Что происходит при изменении ref.current

Изменение ref.current не вызывает повторный ререндер. Это означает:

const ref = useRef(0);
ref.current += 1;
// Компонент не перерендерится, даже если current поменялось

Это делает useRef идеальным для хранения вспомогательных данных, не связанных с UI.

Использование с forwardRef и useImperativeHandle

Компоненты могут передавать ref внутрь себя с помощью forwardRef, а также ограничивать внешний доступ к внутренним методам с помощью useImperativeHandle.

const Input = forwardRef((props, ref) => {
useImperativeHandle(ref, () => ({
focus: () => {
inputRef.current.focus();
}
}));
const inputRef = useRef();
return <input ref={inputRef} />;
});
// Родительский компонент
const App = () => {
const ref = useRef();
return (
<>
&lt;Input ref={ref} /&gt;
&lt;button onClick={() =&gt; ref.current.focus()}>Фокус&lt;/button&gt;
&lt;/&gt;
);
};

Визуальное поведение при ref в JSX

Когда вы пишете ref={someRef}, React автоматически присваивает DOM-элемент в someRef.current:

const elRef = useRef();
return &lt;div ref={elRef}&gt;Привет&lt;/div&gt;;
// elRef.current теперь указывает на &lt;div&gt;

Возможные ошибки при использовании

  • Попытка прочитать ref.current до монтирования компонента (он будет null);

  • Ожидание, что изменение ref.current вызовет обновление интерфейса — этого не произойдёт;

  • Использование useRef в циклах или внутри условий — это нарушает правила хуков.

Подводные камни

  • При SSR ref-объекты не будут содержать ссылки на DOM (всё равно будут null);

  • При использовании с map и списками нужно быть осторожным — не использовать один и тот же ref для нескольких элементов;

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