← Назад к вопросам

Почему существуют useRef и useState если везде могут храниться любые данные?

2.0 Middle🔥 281 комментариев
#React

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Различие между useRef и useState в React

Коллега, это отличный и очень глубокий вопрос, который затрагивает самую суть управления состоянием в React. Действительно, технически и useRef, и useState могут хранить любые данные, но их фундаментальное различие заключается не в ЧТО они хранят, а в КАК они влияют на жизненный цикл компонента и рендеринг.

Ключевая философская разница

useState — это инструмент для реактивного состояния (reactive state), которое напрямую связано с рендер-циклом React. useRef — это инструмент для мутабельных ссылок (mutable references), которые существуют вне потока рендеринга.

// Пример, демонстрирующий фундаментальное различие
function CounterComponent() {
  const [count, setCount] = useState(0); // Реактивное состояние
  const countRef = useRef(0);            // Не-реактивная ссылка
  
  const handleStateClick = () => {
    setCount(count + 1); // Вызовет ре-рендер компонента
    console.log('State count:', count); // Значение "до" обновления
  };
  
  const handleRefClick = () => {
    countRef.current = countRef.current + 1; // НЕ вызовет ре-рендер
    console.log('Ref count:', countRef.current); // Актуальное значение
  };
  
  return (
    <div>
      <p>State: {count}</p> {/* Отображает актуальное значение */}
      <p>Ref: {countRef.current}</p> {/* Не обновится без ре-рендера */}
      <button onClick={handleStateClick}>Increment State</button>
      <button onClick={handleRefClick}>Increment Ref</button>
    </div>
  );
}

Основные различия в поведении

1. Триггеринг ре-рендеров

  • useState: Каждое изменение через setState() вызывает ре-рендер компонента
  • useRef: Изменение .current свойства НЕ вызывает ре-рендер

2. Иммутабельность vs мутабельность

  • useState: Значения должны обновляться через функцию-сеттер, что соответствует философии иммутабельности React
  • useRef: Значение .current можно изменять напрямую, так как это мутабельный объект

Конкретные сценарии использования

Для useState:

  • UI-состояние (открыт/закрыт модальный окно)
  • Данные формы (значения полей ввода)
  • Данные для отображения (список элементов, текущая страница)
  • Состояние загрузки (loading, success, error)
// Типичное использование useState
function TodoList() {
  const [todos, setTodos] = useState([]);
  const [loading, setLoading] = useState(true);
  
  // Изменения в todos или loading вызовут ре-рендер
  // с обновленным интерфейсом
}

Для useRef:

  • Доступ к DOM-элементам (фокус, измерения, анимации)
  • Сохранение предыдущего значения (для сравнения в useEffect)
  • Таймеры и интервалы (хранение ID для очистки)
  • Кэширование дорогих вычислений без триггеринга ре-рендеров
  • Флаг первого рендера (для useEffect с пустым массивом зависимостей)
// Типичное использование useRef
function FocusableInput() {
  const inputRef = useRef(null);
  const previousValueRef = useRef('');
  const timerIdRef = useRef(null);
  
  useEffect(() => {
    // Сохраняем предыдущее значение без ре-рендера
    previousValueRef.current = someValue;
    
    // Управляем таймером
    timerIdRef.current = setTimeout(() => {}, 1000);
    
    // Работаем с DOM
    if (inputRef.current) {
      inputRef.current.focus();
    }
    
    return () => clearTimeout(timerIdRef.current);
  }, [someValue]);
  
  return <input ref={inputRef} />;
}

Практическое правило выбора

Выбирайте useState, когда:

  • Данные должны быть отображены в UI
  • Изменения должны мгновенно отражаться в интерфейсе
  • Нужна история изменений для "отмены"/"повтора"

Выбирайте useRef, когда:

  • Нужно хранить технические данные (ID таймера, индекс анимации)
  • Работаете с побочными эффектами без ре-рендера
  • Храните "сервисные" данные, не связанные с отображением
  • Нужен доступ к DOM-элементам

Важные технические нюансы

// Опасный анти-паттерн - смешивание ответственности
function BadExample() {
  const [count, setCount] = useState(0);
  const intervalRef = useRef(null);
  
  // НЕПРАВИЛЬНО: хранить состояние таймера в state
  const startTimer = () => {
    intervalRef.current = setInterval(() => {
      setCount(prev => prev + 1); // Это правильно
      // Но сам ID интервала должен быть в ref
    }, 1000);
  };
  
  // ПРАВИЛЬНО: технические данные в ref
  const stopTimer = () => {
    clearInterval(intervalRef.current);
  };
}

Итог

Коллега, если кратко: useState — для данных, которые "живут" в мире React и управляют интерфейсом, а useRef — для данных, которые "живут" в мире JavaScript/браузера и обслуживают внутреннюю логику компонента.

React сознательно разделил эти ответственности, чтобы:

  1. Оптимизировать производительность (минимизировать ре-рендеры)
  2. Упростить ментальную модель (четкое разделение "что отображать" и "как работать")
  3. Предотвратить race conditions в асинхронных операциях
  4. Обеспечить прямую работу с DOM, когда это необходимо

Понимание этого разделения критически важно для написания эффективных, предсказуемых и поддерживаемых React-приложений.