Используй ref вместо useState, где возможно!

вс, 1 июня 2025 г. - 2 мин чтения
React hooks: useRef и useState

⚡ Используй ref вместо useState, где возможно!

Все мы любим useState: просто, наглядно, перерисовывает компонент при каждом обновлении. Но иногда это «перерисовывает» — самая большая проблема. 👀

Давайте разберём, когда лучше достать из инструментов useRef, чтобы не трогать рендер‑цикл лишний раз.


Почему useState не всегда лучший выбор

🌀 Каждое изменение вызывает ререндер

Перезаписывая состояние, мы запускаем полный цикл рендера. Для локальных счётчиков или контролируемых полей это нормально. Но когда речь про таймеры, промежуточные значения, значения DOM — мы понапрасну грузим компонент.

🧠 Состояние должно описывать UI

Условный «предыдущий ID запроса» никак не влияет на разметку. Зачем держать его в state, если можно хранить в ref?


Где ref выигрывает у useState

1. 🔄 Таймеры, интервалы, время выполнения

const timerRef = useRef<number | null>(null);
 
useEffect(() => {
  timerRef.current = window.setInterval(() => {
    // ... логика интервала
  }, 1000);
 
  return () => {
    if (timerRef.current !== null) {
      window.clearInterval(timerRef.current);
    }
  };
}, []);

Храним идентификатор таймера в ref — никаких лишних ререндеров, а при unmount можно корректно очистить интервал.

2. 💬 Значения, которые должны пережить рендер

const previousQueryRef = useRef<string>('');
 
useEffect(() => {
  previousQueryRef.current = query;
}, [query]);
 
const isRepeated = previousQueryRef.current === query;

Нам нужно помнить прошлый запрос, но выводить его не нужно. Значит, ref подходит идеально.

3. 🧭 Доступ к DOM без перерисовок

const inputRef = useRef<HTMLInputElement>(null);
 
const focus = () => {
  inputRef.current?.focus();
};

useRef даёт ссылку на DOM‑элемент. Мы можем фокусировать инпут, не дёргая состояние и не обновляя компонент.

4. ⚙️ Внешнее API и сторонние библиотеки

const chartInstanceRef = useRef<Chart | null>(null);
 
useEffect(() => {
  chartInstanceRef.current = initChart(canvasElement);
  return () => chartInstanceRef.current?.destroy();
}, []);

Экземпляр графика нужно хранить между рендерами, но он не меняет UI. Значит, ref — наш выбор.


Когда всё-таки нужен useState

  • 🎚️ Контролируемые поля форм (value, checked)
  • 🌡️ Параметры отображения (открыт ли модал, какой таб выбран)
  • 📊 Данные, которые реально участвуют в рендере

Если значение влияет на шаблон — оставляем его в state. Несмотря на перерендер, это честный и предсказуемый способ обновления UI.


Памятка: state или ref?

ЗадачаuseStateuseRef
Управляемый UI🚫
DOM‑элементы🚫
Таймеры, интервалы🚫
Внешние инстансы (карты, графики)🚫
Кэш значений между рендерами🚫

Вывод

useRef — это не про «где-то храню данные». Это про контроль за тем, что действительно влияет на интерфейс. Если значение нужно только логике, а не верстке — кладём его в ref. Так компонент остаётся быстрым, а код — предсказуемым. 🚀