useRef: ссылки и значения
useRef возвращает объект с полем current, которое можно менять без ререндера. Используется для доступа к DOM-элементам и хранения значений между рендерами.
Синтаксис
const ref = useRef(initialValue);
ref.current — текущее значение. Изменение не вызывает ререндер.
Ссылка на DOM-элемент
import { useRef } from 'react';
function Form() {
const inputRef = useRef<HTMLInputElement>(null);
function focusInput() {
inputRef.current?.focus();
}
return (
<>
<input ref={inputRef} />
<button onClick={focusInput}>Фокус</button>
</>
);
}
После рендера inputRef.current указывает на DOM-элемент input.
Другие операции с DOM
const videoRef = useRef<HTMLVideoElement>(null);
<video ref={videoRef} src="video.mp4" />
<button onClick={() => videoRef.current?.play()}>Play</button>
Хранение значений между рендерами
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef<number | null>(null);
function start() {
intervalRef.current = window.setInterval(() => {
setCount((c) => c + 1);
}, 1000);
}
function stop() {
if (intervalRef.current) {
clearInterval(intervalRef.current);
}
}
return (
<>
<p>{count}</p>
<button onClick={start}>Старт</button>
<button onClick={stop}>Стоп</button>
</>
);
}
intervalRef сохраняет ID интервала между рендерами, но не вызывает их.
useRef vs useState
- useState — изменение вызывает ререндер
- useRef — изменение не вызывает ререндер
Если значение влияет на отображение — useState. Если это служебная переменная (ID таймера, предыдущее значение, ссылка на DOM) — useRef.
Предыдущее значение
function Component({ value }: { value: number }) {
const prevValue = useRef<number | null>(null);
useEffect(() => {
prevValue.current = value;
}, [value]);
return (
<p>Было: {prevValue.current}, стало: {value}</p>
);
}
Сохранение ссылки на функцию
const callbackRef = useRef<() => void>();
useEffect(() => {
callbackRef.current = () => {
console.log('актуальная функция');
};
});
// вызвать актуальную версию
callbackRef.current?.();
Рендер-независимые значения
const renderCount = useRef(0);
renderCount.current++;
return <p>Рендеров: {renderCount.current}</p>;
Хотя current меняется при каждом рендере — ререндер не вызывается, потому что это не стейт.
forwardRef
Если компонент должен принимать ref извне:
import { forwardRef, useRef } from 'react';
const Input = forwardRef<HTMLInputElement, { placeholder?: string }>(
({ placeholder }, ref) => {
return <input ref={ref} placeholder={placeholder} />;
}
);
// Использование
const inputRef = useRef<HTMLInputElement>(null);
<Input ref={inputRef} />
В React 19 ref передаётся как обычный пропс, forwardRef больше не нужен.
Итоги
useRefвозвращает объект с полемcurrent- Изменение
currentне вызывает ререндер - Для доступа к DOM —
useRef<HTMLInputElement>(null) - Для хранения значений между рендерами
- Отличие от
useState— отсутствие ререндера