useRef хранит изменяемое значение между рендерами, но изменение ref не запускает новый рендер. Чаще всего ref используют для доступа к DOM-элементу или хранения технического значения: id таймера, предыдущего значения, флага.
Синтаксис useRef
import { useRef } from 'react';
const inputRef = useRef(null);
Фокус на input
function SearchBox() {
const inputRef = useRef(null);
function focusInput() {
inputRef.current.focus();
}
return (
<div>
<input ref={inputRef} placeholder="Поиск" />
<button onClick={focusInput}>Фокус</button>
</div>
);
}
После рендера inputRef.current будет ссылкой на настоящий DOM-элемент input. До рендера там null.
useRef и useState
| useState | useRef |
|---|---|
| Изменение запускает рендер | Изменение не запускает рендер |
| Используется для данных интерфейса | Используется для технических значений |
| Значение читается в JSX | Значение обычно не показывают напрямую |
Хранить id таймера
function Stopwatch() {
const [seconds, setSeconds] = useState(0);
const intervalRef = useRef(null);
function start() {
if (intervalRef.current) return;
intervalRef.current = setInterval(() => {
setSeconds(s => s + 1);
}, 1000);
}
function stop() {
clearInterval(intervalRef.current);
intervalRef.current = null;
}
return (
<>
<p>{seconds}</p>
<button onClick={start}>Старт</button>
<button onClick={stop}>Стоп</button>
</>
);
}
Чего не делать с ref
- Не храните в ref данные, которые должны отображаться на экране.
- Не читайте и не меняйте ref.current во время рендера без причины.
- Не используйте ref как замену нормальному потоку данных через props и state.
Пример: предыдущее значение
useRef удобно хранит значение между рендерами. Например, можно показать пользователю предыдущее значение счётчика.
function PreviousCounter() {
const [count, setCount] = useState(0);
const prevCountRef = useRef(null);
useEffect(() => {
prevCountRef.current = count;
}, [count]);
return (
<div>
<p>Сейчас: {count}</p>
<p>Было: {prevCountRef.current ?? 'нет значения'}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
);
}
Изменение prevCountRef.current не запускает рендер. Рендер запускает setCount, а ref просто хранит информацию между этими рендерами.
Пример: измерить размер блока
function MeasureBox() {
const boxRef = useRef(null);
function showSize() {
const rect = boxRef.current.getBoundingClientRect();
alert(`Ширина: ${rect.width}px`);
}
return (
<>
<div ref={boxRef}>Измеряемый блок</div>
<button onClick={showSize}>Показать размер</button>
</>
);
}
Главный ориентир
Если значение должно отображаться на экране, берите useState. Если значение нужно коду, но не должно само перерисовывать компонент, берите useRef.
Эта граница особенно важна в формах и таймерах. Если вы спрятали видимое значение в ref, интерфейс может отстать от данных. Если положили технический id в state, получите лишние рендеры без пользы.
Когда useRef лучше useState
useRef нужен для значений, которые должны пережить рендер, но не должны сами запускать новый рендер. Это техническое хранилище, а не место для данных интерфейса.
Типичный пример — id таймера, DOM-элемент, предыдущее значение или флаг, который нужен обработчику, но не должен менять разметку. Если же значение выводится в JSX, ref почти наверняка выбран неправильно.
Хорошие сценарии
- сфокусировать input после клика;
- хранить id таймера;
- запомнить предыдущее значение;
- измерить размер DOM-элемента;
- держать флаг, который нужен обработчику, но не показывается на экране.
Плохой сценарий
Если значение должно отображаться пользователю, не прячьте его в ref. Пользователь увидит изменения только после какого-то другого рендера, и код станет непредсказуемым. Для видимых данных используйте useState.
Простая проверка
Изменение значения должно сразу менять экран? Берите useState. Значение нужно только коду и не должно перерисовывать компонент? Берите useRef.
Если ответ не очевиден, начните с useState и посмотрите, нужно ли значение пользователю на экране. В useRef переносите только то, что должно сохраниться между рендерами, но не участвует в отображении.



