Как работает 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 (
<>
<Input ref={ref} />
<button onClick={() => ref.current.focus()}>Фокус</button>
</>
);
};
Визуальное поведение при ref в JSX
Когда вы пишете ref={someRef}, React автоматически присваивает DOM-элемент в someRef.current:
const elRef = useRef();
return <div ref={elRef}>Привет</div>;
// elRef.current теперь указывает на <div>
Возможные ошибки при использовании
-
Попытка прочитать ref.current до монтирования компонента (он будет null);
-
Ожидание, что изменение ref.current вызовет обновление интерфейса — этого не произойдёт;
-
Использование useRef в циклах или внутри условий — это нарушает правила хуков.
Подводные камни
-
При SSR ref-объекты не будут содержать ссылки на DOM (всё равно будут null);
-
При использовании с map и списками нужно быть осторожным — не использовать один и тот же ref для нескольких элементов;
-
Если использовать useRef для хранения данных в сложной логике (например, в виде глобального контейнера), можно нечаянно нарушить чистоту компонента.