ReactJuniorTechnical
Что такое useRef и когда его стоит использовать вместо useState?
useRef возвращает мутируемый объект {current}, изменение которого не вызывает ре-рендер. Используется для хранения ссылок на DOM-узлы, сохранения значений между рендерами без ререндера, и хранения предыдущих значений.
Что такое useRef
useRef(initialValue) возвращает объект { current: T }, который живёт всё время жизни компонента. Ключевое отличие от useState: изменение ref.current не вызывает повторного рендера.
Три сценария использования
1. Доступ к DOM-узлу
import { useRef } from 'react';
export function SearchInput() {
const inputRef = useRef<HTMLInputElement>(null);
function handleClick() {
inputRef.current?.focus(); // прямой вызов DOM API
}
return (
<>
<input ref={inputRef} type="text" placeholder="Поиск..." />
<button onClick={handleClick}>Фокус</button>
</>
);
}
2. Хранение значения между рендерами без ре-рендера
import { useRef, useEffect } from 'react';
export function Timer() {
const intervalId = useRef<ReturnType<typeof setInterval> | null>(null);
useEffect(() => {
intervalId.current = setInterval(() => console.log('tick'), 1000);
return () => {
if (intervalId.current) clearInterval(intervalId.current);
};
}, []);
function stop() {
if (intervalId.current) clearInterval(intervalId.current);
}
return <button onClick={stop}>Стоп</button>;
}
3. Хранение предыдущего значения
import { useRef, useEffect } from 'react';
function usePrevious<T>(value: T): T | undefined {
const ref = useRef<T | undefined>(undefined);
useEffect(() => {
ref.current = value; // Обновляем после рендера
});
return ref.current; // Возвращает значение до текущего рендера
}
export function Counter({ count }: { count: number }) {
const prev = usePrevious(count);
return <p>Было: {prev}, стало: {count}</p>;
}
useRef vs useState — когда что использовать
- useState: значение отображается в UI и его изменение должно обновить экран.
- useRef: значение нужно только для логики (ID таймера, прошлые данные, DOM-элемент), отображать его не нужно.
Частые паттерны
// Avoid stale closure в обработчике
import { useRef, useCallback } from 'react';
function useLatestCallback<T extends (...args: unknown[]) => unknown>(fn: T) {
const ref = useRef(fn);
ref.current = fn; // Всегда актуальная версия
return useCallback((...args: Parameters<T>) => ref.current(...args), []);
}
Подводные камни
- Чтение ref.current во время рендера:
ref.currentможет бытьnullпри первом рендере (до монтирования). Доступ к DOM-ref безопасен только в эффектах и обработчиках событий. - Мутация ref не обновляет UI: если нужно отобразить значение — используйте
useState; хранение счётчика в ref и попытка показать его в JSX не сработает. - Передача ref как обычного пропа: до React 19 ref не передаётся через пропы — нужен
forwardRef. - Инициализация с new Date(): если передать
useRef(expensiveComputation()), функция вызывается при каждом рендере (только результат игнорируется) — используйте ленивую инициализацию через хук илиuseMemo. - ref.current = null при размонтировании: React обнуляет ref после размонтирования, поэтому всегда проверяйте
ref.currentперед использованием.
Common mistakes
- Хранить в
refвизуально важное значение и удивляться, что UI не обновляется. - Читать DOM-
refво время render-фазы. - Дублировать
useStateиuseRefдля одного значения «на всякий случай».
What the interviewer is testing
- Может ли назвать оба основных применения рефов.
- Знает ли, что присваивание ref не вызывает рендер.
- Понимает ли изменение API ref в React 19.