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

В чем разница между useRef и useState при хранении значения?

1.0 Junior🔥 271 комментариев
#React

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

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

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

В чем разница между useRef и useState при хранении значения

useRef и useState оба хранят значения, но имеют фундаментально разные поведения. Выбор между ними влияет на перерисовку компонента, способность отследить изменения и производительность.

useState — для состояния которое должно влиять на UI

useState обновляет UI при изменении значения:

function Counter() {
  const [count, setCount] = useState(0);
  
  console.log('Component rendered');
  
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}

// При клике:
// 1. setCount вызывается
// 2. React увидит новое значение count
// 3. Компонент перерисуется
// 4. Console логирует "Component rendered"

useRef — для значения которое НЕ должно влиять на UI

useRef НЕ обновляет UI при изменении значения:

function CounterRef() {
  const countRef = useRef(0);
  const [dummy, setDummy] = useState(0); // для перерисовки
  
  console.log('Component rendered');
  
  return (
    <div>
      <p>Count: {countRef.current}</p>
      <button onClick={() => {
        countRef.current += 1;
        // UI не обновится автоматически!
        console.log('countRef.current:', countRef.current); // логирует
      }}>+1 (без перерисовки)</button>
      
      <button onClick={() => setDummy(dummy + 1)}>
        Перерисовать компонент
      </button>
    </div>
  );
}

// При клике на первую кнопку:
// 1. countRef.current изменяется
// 2. Компонент НЕ перерисуется
// 3. Console НЕ логирует "Component rendered"
// 4. Но значение в консоли правильное!

Таблица сравнения

АспектuseStateuseRef
Перерисовка✅ Вызывает перерисовку❌ НЕ вызывает
ПрисвоениеsetState(value)ref.current = value
Доступvaluevalue.current
Мутация❌ Запрещена✅ Разрешена
Начальное значениеФункция или значениеФункция или значение
Синхронность❌ Асинхронно✅ Синхронно
Сохранение между рендерами✅ Да✅ Да
Обновление DOM✅ При изменении❌ Нужно вручную

Пример 1: Счётчик кликов

С useState (правильный способ для UI):

function ClickCounter() {
  const [clicks, setClicks] = useState(0);
  
  return (
    <div>
      <p>Кликов: {clicks}</p> {/* Обновляется автоматически */}
      <button onClick={() => setClicks(clicks + 1)}>Click</button>
    </div>
  );
}

С useRef (неправильный способ для UI):

function ClickCounterRef() {
  const clicksRef = useRef(0);
  const [, forceUpdate] = useState(0);
  
  const handleClick = () => {
    clicksRef.current += 1;
    forceUpdate(clicksRef.current); // Нужно вручную перерисовать!
  };
  
  return (
    <div>
      <p>Кликов: {clicksRef.current}</p>
      <button onClick={handleClick}>Click</button>
    </div>
  );
}

Не делай так! Используй useState для UI.

Пример 2: Доступ к DOM элементу

Правильное использование useRef:

function TextInput() {
  const inputRef = useRef(null);
  
  const focusInput = () => {
    inputRef.current.focus(); // Прямой доступ к DOM
  };
  
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={focusInput}>Focus</button>
    </div>
  );
}

// useRef не вызывает перерисовку
// Это просто ссылка на DOM элемент

Пример 3: Хранение предыдущего значения

Отслеживание изменений:

function PreviousValue({ name }) {
  const prevNameRef = useRef();
  const [renderCount, setRenderCount] = useState(0);
  
  useEffect(() => {
    // Сохранить предыдущее значение ДО следующего рендера
    prevNameRef.current = name;
  }, [name]);
  
  return (
    <div>
      <p>Текущее: {name}</p>
      <p>Предыдущее: {prevNameRef.current}</p>
      <p>Рендеров: {renderCount}</p>
      <button onClick={() => setRenderCount(renderCount + 1)}>
        Перерисовать
      </button>
    </div>
  );
}

// useRef НЕ вызывает перерисовку
// Идеален для отслеживания старых значений

Пример 4: Таймер

Нужно сохранить ID таймера:

function Stopwatch() {
  const [seconds, setSeconds] = useState(0);
  const intervalRef = useRef(null); // ✅ useRef для ID
  
  const start = () => {
    if (intervalRef.current) return; // Уже запущен
    
    intervalRef.current = setInterval(() => {
      setSeconds(prev => prev + 1); // Обновить UI
    }, 1000);
  };
  
  const stop = () => {
    if (intervalRef.current) {
      clearInterval(intervalRef.current);
      intervalRef.current = null;
    }
  };
  
  return (
    <div>
      <p>Времени: {seconds}s</p>
      <button onClick={start}>Start</button>
      <button onClick={stop}>Stop</button>
    </div>
  );
}

// useRef сохраняет ID между рендерами
// Без перерисовки компонента

Пример 5: Счётчик БЕЗ обновления UI

Иногда нужно считать, но не обновлять UI:

function HiddenCounter() {
  const countRef = useRef(0);
  
  const handleClick = () => {
    countRef.current += 1;
    // Считается в фоне, UI не обновляется
    console.log('Действительных кликов:', countRef.current);
  };
  
  const logCount = () => {
    alert(`Всего кликов: ${countRef.current}`);
  };
  
  return (
    <div>
      <button onClick={handleClick}>Click (не видно счёта)</button>
      <button onClick={logCount}>Show Count</button>
    </div>
  );
}

// Счётчик работает, но UI не переживает излишних перерисовок

Когда использовать useState

Используй useState когда:

  • Значение должно отражаться в UI
  • Нужна перерисовка компонента
  • Данные влияют на вывод компонента
  • Типичное состояние приложения (form values, filters, etc.)

Когда использовать useRef

Используй useRef когда:

  • Нужна прямая ссылка на DOM элемент
  • Нужно сохранить значение БЕЗ перерисовки
  • Хранишь ID таймера/интервала
  • Отслеживаешь предыдущее значение
  • Кешируешь данные которые не меняют UI

Частые ошибки

❌ Ошибка 1: useRef вместо useState

// Неправильно!
function Form() {
  const nameRef = useRef('');
  return (
    <div>
      <input 
        ref={nameRef}
        onChange={(e) => nameRef.current = e.target.value}
      />
      <p>{nameRef.current}</p> {/* НЕ обновится! */}
    </div>
  );
}

// Правильно!
function Form() {
  const [name, setName] = useState('');
  return (
    <div>
      <input value={name} onChange={(e) => setName(e.target.value)} />
      <p>{name}</p> {/* Обновится! */}
    </div>
  );
}

❌ Ошибка 2: useState для DOM ссылок

// Неправильно!
function Video() {
  const [videoRef, setVideoRef] = useState(null);
  return <video ref={videoRef} />;  // Вызывает лишние перерисовки
}

// Правильно!
function Video() {
  const videoRef = useRef(null);
  return <video ref={videoRef} />;  // Никаких перерисовок
}

Золотое правило: если значение должно быть видно в UI — используй useState. Если это вспомогательное значение для логики или DOM ссылка — используй useRef.

В чем разница между useRef и useState при хранении значения? | PrepBro