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

Вызывает ли изменение useRef перерисовку компонента

2.0 Middle🔥 261 комментариев
#HTML и CSS

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

🐱
claude-haiku-4.5PrepBro AI2 апр. 2026 г.(ред.)

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

Вызывает ли изменение useRef перерисовку компонента

НЕТ. Изменение useRef НЕ вызывает перерисовку компонента. Это одно из ключевых отличий useRef от useState. Расскажу подробно.

1. useRef — хранилище без перерисовки

import { useRef, useState } from 'react';

function Counter() {
  const countRef = useRef(0);
  const [count, setCount] = useState(0);
  
  const handleRef = () => {
    countRef.current++; // Изменяем, но перерисовки НЕ будет
    console.log('countRef:', countRef.current); // 1, 2, 3...
  };
  
  const handleState = () => {
    setCount(count + 1); // Изменяем и вызываем перерисовку
  };
  
  return (
    <div>
      <p>Ref: {countRef.current}</p> <!-- Всегда 0 на экране -->
      <p>State: {count}</p> <!-- Обновляется на экране -->
      <button onClick={handleRef}>Increment Ref</button>
      <button onClick={handleState}>Increment State</button>
    </div>
  );
}

Результат:

  • Кнопка 1 нажата 5 раз: countRef.current = 5, но на экране всё ещё показывает 0
  • Кнопка 2 нажата 1 раз: count = 1, на экране обновляется

2. Откуда это видно

useRef не вызывает re-render:

function Example() {
  const renderCount = useRef(0);
  
  // Эта функция вызывается при КАЖДОМ render
  renderCount.current++;
  
  console.log('Component rendered, times:', renderCount.current);
  
  return (
    <div>
      <p>This component rendered {renderCount.current} times</p>
      <button onClick={() => {
        // Это НЕ вызовет re-render
        renderCount.current = 100;
      }}>
        Change Ref (no re-render)
      </button>
    </div>
  );
}

Вывод в консоль:

Component rendered, times: 1
Component rendered, times: 2 (после нажатия кнопки)
Component rendered, times: 3

На экране:

  • После нажатия кнопки: "This component rendered 2 times" (потому что перерисовки не было, значение 100 не отобразилось)

3. Сравнение useRef vs useState

function Comparison() {
  const ref = useRef(0);
  const [state, setState] = useState(0);
  const renderCount = useRef(0);
  
  renderCount.current++;
  
  return (
    <div>
      <p>Renders: {renderCount.current}</p>
      <p>Ref value: {ref.current}</p>
      <p>State value: {state}</p>
      
      <button onClick={() => {
        ref.current++; // Не вызывает re-render
      }}>Change Ref</button>
      
      <button onClick={() => {
        setState(s => s + 1); // Вызывает re-render
      }}>Change State</button>
    </div>
  );
}

Нажимаем кнопку "Change Ref" 5 раз:

  • Renders: 1 (не увеличивается)
  • Ref value: 5 (увеличивается в консоли, но не видно на экране)

Нажимаем кнопку "Change State" 1 раз:

  • Renders: 2 (увеличивается, произошла перерисовка)
  • State value: 1 (видно на экране)

4. Практическое применение: фокус на input

function TextInput() {
  const inputRef = useRef(null);
  
  const handleFocus = () => {
    // Получаем доступ к DOM элементу
    inputRef.current.focus();
    // Это НЕ вызывает перерисовку компонента
  };
  
  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>Focus Input</button>
    </div>
  );
}

Почему это важно: Нам нужно управлять фокусом, но это не требует перерисовки. useState был бы излишним.

5. Практическое применение: таймеры

function Timer() {
  const intervalRef = useRef(null);
  const timeRef = useRef(0);
  const [display, setDisplay] = useState(0);
  
  const handleStart = () => {
    // Сохраняем ID интервала в ref
    intervalRef.current = setInterval(() => {
      timeRef.current++; // Это НЕ вызывает перерисовку
      setDisplay(timeRef.current); // Это ВЫЗЫВАЕТ перерисовку
    }, 1000);
  };
  
  const handleStop = () => {
    clearInterval(intervalRef.current);
  };
  
  return (
    <div>
      <p>Time: {display}</p>
      <button onClick={handleStart}>Start</button>
      <button onClick={handleStop}>Stop</button>
    </div>
  );
}

6. useRef сохраняет значение между renders

function Example() {
  const ref = useRef(0);
  const [, setTrigger] = useState(0);
  
  // Эта функция вызывается при КАЖДОМ render
  ref.current++;
  
  return (
    <div>
      <p>Ref current: {ref.current}</p>
      <button onClick={() => setTrigger(t => t + 1)}>
        Trigger Re-render
      </button>
    </div>
  );
}

Первый render:

  • ref.current = 1
  • На экране: "Ref current: 1"

После нажатия кнопки (re-render):

  • ref.current = 2 (значение сохранилось и увеличилось!)
  • На экране: "Ref current: 2"

7. useRef для хранения предыдущего значения

function Component({ value }) {
  const prevValueRef = useRef();
  
  useEffect(() => {
    prevValueRef.current = value; // Сохраняем текущее значение
  }, [value]);
  
  return (
    <div>
      <p>Current: {value}</p>
      <p>Previous: {prevValueRef.current}</p>
    </div>
  );
}

Когда value изменяется:

  1. Компонент перерисовывается (потому что value в зависимостях)
  2. На экране показываем текущее значение
  3. В useEffect сохраняем это значение в ref
  4. При следующем изменении value, в prevValueRef.current будет старое значение

8. Когда useRef вызывает обновление экрана

Некоторые операции вызывают КОСВЕННЫЙ re-render:

function Example() {
  const ref = useRef({ count: 0 });
  
  const handleClick = () => {
    // Это изменяет объект в памяти
    ref.current.count++;
    
    // НО это НЕ вызовет re-render
    // ref.current всё ещё указывает на тот же объект
  };
  
  return (
    <div>
      <p>Count: {ref.current.count}</p>
      <button onClick={handleClick}>Increment</button>
    </div>
  );
}

На экране всегда будет 0, даже если count = 100 в памяти.

9. useCallback и useRef

function Parent() {
  const callCountRef = useRef(0);
  const [, setTrigger] = useState(0);
  
  const memoizedCallback = useCallback(() => {
    callCountRef.current++; // Не вызывает re-render
    console.log('Called', callCountRef.current);
  }, []);
  
  return (
    <div>
      <p>Call count: {callCountRef.current}</p>
      <button onClick={memoizedCallback}>Call Callback</button>
      <button onClick={() => setTrigger(t => t + 1)}>Re-render</button>
    </div>
  );
}

Нажимаем "Call Callback" 5 раз:

  • Консоль: Called 1, Called 2, Called 3...
  • Экран: "Call count: 0" (не обновляется)

Нажимаем "Re-render":

  • Экран обновляется и показывает "Call count: 5"

10. Чтобы useRef вызвал re-render

Нужно явно вызвать setState:

function Example() {
  const ref = useRef(0);
  const [, setRender] = useState(0);
  
  const handleClick = () => {
    ref.current++;
    // Явно вызываем re-render
    setRender(prev => prev + 1);
  };
  
  return (
    <div>
      <p>Ref: {ref.current}</p>
      <button onClick={handleClick}>Increment and Re-render</button>
    </div>
  );
}

11. useLayoutEffect и useRef

function Example() {
  const ref = useRef(null);
  
  useLayoutEffect(() => {
    // useLayoutEffect вызывается ПОСЛЕ render, но ДО paint
    if (ref.current) {
      ref.current.focus();
      // Это НЕ вызывает НОВЫЙ re-render
    }
  }, []);
  
  return <input ref={ref} />;
}

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

СвойствоuseRefuseState
Вызывает re-renderНЕТДА
Сохраняет между rendersДАДА
Возвращает новый объектНЕТ (тот же ref)ДА (новое значение)
Синхронный доступДАНЕТ (асинхронный)
Использование дляDOM, таймеры, переменныеВидимые данные

Ключевой вывод

НЕТ, изменение useRef НЕ вызывает перерисовку компонента.

УseRef используется для:

  1. Доступа к DOM элементам (фокус, значение input)
  2. Хранения значений, которые не влияют на UI (таймеры, счётчики)
  3. Сохранения значений между renders (предыдущее значение, ID интервала)

Если нужна перерисовка при изменении значения — используй useState.

Если нужно только хранить значение без перерисовки — используй useRef.

Вызывает ли изменение useRef перерисовку компонента | PrepBro