useRef: когда React-компоненту нужно запомнить что-то между рендерами

#react#hooks#performance

Вчера снова наступил на грабли: компонент с анимацией дергал DOM-элемент при каждом обновлении пропсов, хотя визуально ничего не менялось. Вот тогда и вспомнил про useRef — хуковый аналог createRef, который не триггерит ререндеры при изменении своего .current.

Как работает useRef под капотом

useRef возвращает обычный JS-объект с единственным свойством current. Магия в том, что этот объект сохраняется между рендерами компонента:

const intervalRef = useRef(null); // { current: null }

// Где-то в эффекте
intervalRef.current = setInterval(() => {...}, 1000);

// При следующем рендере intervalRef.current сохранит значение

Важное правило: не трогайте ref.current во время рендера. React ожидает, что рендеры остаются чистыми функциями. Вместо этого работайте с рефами в useEffect, обработчиках событий или других side-effect-областях.

Три реальных сценария из моей практики

1. Работа с DOM-нодами

Самый частый кейс — когда нужно напрямую взаимодействовать с элементом:

const inputRef = useRef(null);

const focusInput = () => inputRef.current?.focus();

return (
  <>
    <input ref={inputRef} />
    <button onClick={focusInput}>Focus</button>
  </>
);

Здесь реф — единственно правильный способ, потому что:

2. Хранение мутабельных значений

Бывают значения, которые должны сохраняться между рендерами, но не влияют на отрисовку. Например, ID таймера:

const timerRef = useRef(null);

useEffect(() => {
  timerRef.current = setTimeout(...);
  return () => clearTimeout(timerRef.current);
}, []);

Если бы хранили в state — при очистке таймера получили бы ненужный ререндер.

3. Сравнение предыдущих пропсов

Иногда нужно понять, изменилось ли конкретное значение:

const prevValueRef = useRef();

useEffect(() => {
  if (prevValueRef.current !== someProp) {
    // Логика при изменении
  }
  prevValueRef.current = someProp;
}, [someProp]);

Где useRef может подвести

  1. Несинхронные обновления: изменение ref.current не вызывает мгновенного обновления компонента. Если нужно отреагировать на изменение — сочетайте с state.

  2. Утечки памяти: объект рефа живет до unmount компонента. Если храните тяжелые ресурсы (WebSocket, большие объекты) — не забывайте очищать в useEffect cleanup.

  3. Тестирование: мокировать рефы в тестах сложнее, чем пропсы. Для тестируемых компонентов иногда лучше выносить логику в кастомные хуки.

Альтернативы и похожие инструменты

На практике я часто комбинирую useRef с useEffect для управления сторонними библиотеками (карты, графики) или сложными таймаутами. Главное — помнить, что это именно escape hatch, а не замена state-менеджменту.

Если ваш компонент начал обрастать кучей useRef — возможно, пришло время вынести логику в кастомный хук или пересмотреть архитектуру. Как и с любым мощным инструментом, важно чувствовать момент, когда его применение из бенефита превращается в технический долг.


Источник: https://dev.to/mohandassmani/useref-in-react-79j