Урок 04

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 — отсутствие ререндера