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

Будет ли вызван useEffect, если у него в зависимостях изменился Ref?

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

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

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

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

Будет ли вызван useEffect, если в зависимостях изменился Ref?

Ответ: НЕТ, не будет. Это одна из самых частых ошибок в React. Давайте разберёмся почему.

Почему Ref не триггерит useEffect

useEffect сравнивает зависимости по identity (===), а не по value (значению).

Ref всегда один и тот же объект, даже если его .current изменился:

function Counter() {
  const countRef = useRef(0);
  
  useEffect(() => {
    console.log('Effect вызван!');
    console.log('countRef.current:', countRef.current);
  }, [countRef]); // countRef в зависимостях
  
  const handleClick = () => {
    countRef.current++;
    console.log('countRef изменился на:', countRef.current);
  };
  
  return (
    <div>
      <p>Count: {countRef.current}</p>
      <button onClick={handleClick}>Increment Ref</button>
    </div>
  );
}

// Поведение:
// 1. При монтировании: "Effect вызван! countRef.current: 0"
// 2. Клик на кнопку: "countRef изменился на: 1"
// 3. Effect НЕ вызывается снова!
// 4. countRef.current = 1, но компонент не переренденился

Почему это происходит

const ref1 = useRef(0);
const ref2 = useRef(0);

ref1.current = 100;

console.log(ref1 === ref1);           // true - один и тот же объект
console.log(ref1.current === 100);    // true - значение изменилось
console.log(ref1 === ref2);           // false - разные рефы

Ref не изменяется, изменяется только его свойство .current. React сравнивает сам Ref, а не его содержимое:

function Demo() {
  const ref = useRef(0);
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log('Зависимость ref изменилась');
  }, [ref]); // ref НИКОГДА не изменится, Effect не вызовется
  
  useEffect(() => {
    console.log('Зависимость count изменилась');
  }, [count]); // count изменяется, Effect вызовется
  
  return (
    <div>
      <button onClick={() => {
        ref.current++; // не триггерит Effect
      }}>Update Ref</button>
      
      <button onClick={() => {
        setCount(count + 1); // триггерит Effect
      }}>Update Count</button>
    </div>
  );
}

Распространённая ошибка

// НЕПРАВИЛЬНО - Effect не вызовется при изменении ref.current
function BadExample() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    // Хочу фокусировать input когда ref изменился
    inputRef.current?.focus();
  }, [inputRef]); // inputRef в зависимостях, но это не сработает
  
  return <input ref={inputRef} />;
}

// ПРАВИЛЬНО - использовать пустой массив зависимостей
function GoodExample() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    inputRef.current?.focus();
  }, []); // пусто, Effect вызовется один раз при монтировании
  
  return <input ref={inputRef} />;
}

Когда это может быть проблемой

Пример 1: Отслеживание значения ref

function Timer() {
  const timerRef = useRef(null);
  
  useEffect(() => {
    // Хочу очистить таймер когда что-то изменилось
    return () => {
      clearInterval(timerRef.current);
    };
  }, [timerRef]); // это НЕ сработает!
  
  const startTimer = () => {
    timerRef.current = setInterval(() => {
      console.log('Tick');
    }, 1000);
  };
  
  return <button onClick={startTimer}>Start</button>;
}

Пример 2: Отслеживание изменений внутри ref

function DataComponent() {
  const dataRef = useRef({ count: 0 });
  
  useEffect(() => {
    console.log('Data изменилась:', dataRef.current);
  }, [dataRef]); // Effect НЕ вызовется даже если dataRef.current изменилась
  
  const updateData = () => {
    dataRef.current.count++; // изменили, но Effect не запустится
  };
  
  return <button onClick={updateData}>Update</button>;
}

Как правильно отслеживать изменения ref

Вариант 1: Используй state вместо ref

// Хорошо - state триггерит re-render и useEffect
function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    console.log('Count изменился:', count);
  }, [count]); // count в зависимостях
  
  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}

Вариант 2: Используй useCallback для отслеживания

function FormComponent() {
  const [formData, setFormData] = useState({});
  const formRef = useRef(null);
  
  // Отслеживаем объект formData, а не сам ref
  useEffect(() => {
    console.log('Form data изменилась:', formData);
  }, [formData]); // formData в зависимостях
  
  return <form ref={formRef}>{/* ... */}</form>;
}

Вариант 3: Вызови effect вручную при изменении ref.current

function ManualEffect() {
  const valueRef = useRef(0);
  
  const handleChangeRef = (newValue) => {
    valueRef.current = newValue;
    
    // Вызови логику вручную
    console.log('Ref изменился на:', valueRef.current);
    doSomething(valueRef.current);
  };
  
  return (
    <button onClick={() => handleChangeRef(valueRef.current + 1)}>
      Update
    </button>
  );
}

Ref vs State: сравнение

ХарактеристикаRefState
Триггерит re-renderНетДа
Триггерит useEffectНетДа
Можно обновить синхронноДаНет
Отслеживается в зависимостяхНет (бесполезно)Да
Используй дляDOM доступ, переменныеUI обновления
function Comparison() {
  const ref = useRef(0);
  const [state, setState] = useState(0);
  
  useEffect(() => {
    console.log('Effect вызван');
  }, [ref, state]);
  
  const updateRef = () => {
    ref.current++; // синхронно, Effect не запустится
  };
  
  const updateState = () => {
    setState(state + 1); // асинхронно, Effect запустится
  };
  
  return (
    <div>
      <button onClick={updateRef}>Ref: {ref.current}</button>
      <button onClick={updateState}>State: {state}</button>
    </div>
  );
}

Правильные паттерны

Паттерн 1: useRef для DOM

function InputFocus() {
  const inputRef = useRef(null);
  
  useEffect(() => {
    // Фокусируй один раз при монтировании
    inputRef.current?.focus();
  }, []); // пусто
  
  return <input ref={inputRef} />;
}

Паттерн 2: useRef для переменных

function PreviousValue() {
  const [count, setCount] = useState(0);
  const prevCountRef = useRef();
  
  useEffect(() => {
    // Отслеживаем предыдущее значение через state
    prevCountRef.current = count;
  }, [count]); // зависим от state, не от ref
  
  return <p>Current: {count}, Previous: {prevCountRef.current}</p>;
}

Итог

useEffect НЕ вызывается если изменился ref.current

Причина: React сравнивает сам объект Ref, а не его содержимое. Ref всегда один и тот же.

Используй:

  • state для данных, которые должны триггерить Effect
  • ref для доступа к DOM или сохранения значений без триггера Effect
  • Не добавляй Ref в зависимости useEffect

Если нужно отслеживать изменения ref.current, переделай на state или отслеживай state, а не ref.

Будет ли вызван useEffect, если у него в зависимостях изменился Ref? | PrepBro