Какие данные помимо DOM можно хранить в useRef?
Хуки
useRef — универсальный контейнер: помимо DOM-узлов в нём хранят любые мутируемые значения, изменение которых не должно вызывать ререндер.
Типичные категории данных
Идентификаторы таймеров:
const timerRef = useRef(null);
timerRef.current = setTimeout(save, 2000); // старт
clearTimeout(timerRef.current); // отмена (дебаунс, автосейв)Инстансы внешних объектов — живут дольше рендера, не сериализуются: WebSocket, AbortController, экземпляры библиотек (Chart, Map, редактор), IntersectionObserver.
Предыдущие значения props/state:
const prevStatus = useRef(status);
useEffect(() => { prevStatus.current = status; });
// сравнить «было/стало» в следующем рендереФлаги жизненного цикла и защиты: isMounted, «запрос уже отправлен», «первый рендер» (пропустить эффект при монтировании), счётчик рендеров при отладке.
«Свежие» значения для стабильных колбэков — обход устаревших замыканий:
const handlerRef = useRef(onEvent);
useEffect(() => { handlerRef.current = onEvent; });
// подписка создаётся один раз, но зовёт всегда актуальный обработчик
useEffect(() => {
const sub = source.subscribe((e) => handlerRef.current(e));
return () => sub.unsubscribe();
}, []);Кеши и аккумуляторы: результаты вычислений между рендерами, буферы событий, позиция скролла до размонтирования.
Критерий выбора ref против state
Отображается в UI → state (нужен ререндер). Служебное значение, нужное «между рендерами», → ref. Ошибка в обе стороны: отображаемое в ref «замораживает» UI, служебное в state — лишние ререндеры.
Коротко для собеседования
В useRef хранят всё мутируемое и не влияющее на рендер: id таймеров, инстансы (WebSocket, чарты, observers), предыдущие значения, флаги (isMounted, первый рендер), «свежие» колбэки для стабильных подписок, кеши. Правило: видно пользователю — state, служебное — ref.