Будет ли вызван useEffect, если у него в зависимостях изменился Ref?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Будет ли вызван 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: сравнение
| Характеристика | Ref | State |
|---|---|---|
| Триггерит 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.