Я исправил ваш код и постараюсь объяснить, что, надеюсь, правильно. Я также добавил альтернативу, использующую setInterval
вместо повторного запуска setTimeout
.
https://codesandbox.io/s/serene-ives-502hz
Проблема заключается в повторной визуализации компонента. Каждый раз, когда свойство или объект состояния изменяется, компонент перерисовывается, поэтому все объекты внутри создаются снова. Это также верно для updateCount
. Но метод внутри useEffect
будет воссоздан только при изменении одного из параметров прослушивания. В вашем случае массив пуст, поэтому он будет запускаться только один раз при монтировании Counter
компонента. Так что ссылка на updateCount
не такая же, как внутри компонента после рендеринга из-за setCount
.
Чтобы обойти эту проблему useRef
приходит на помощь. При этом может быть создана переменная "stati c". Так что это не будет воссоздано при повторной визуализации компонента. Таким образом, мы можем сохранить здесь ссылку на обработчик таймера и вызвать clearTimeout
. Следующая вещь - это доступ count
.
Я не уверен, правильно ли это объяснение: setCount
не обновит текущий count
, но создаст новый объект. Поэтому после вызова setCount
текущее значение count
остается прежним. Для этого setCount
можно вызвать по-другому, предоставив функцию, которая будет вызываться внутри с текущим значением count
. Чтобы иметь возможность вывести console.log
, просто добавьте еще один useEffect
, который будет вызываться каждый раз, когда count
изменяется при прослушивании count
.
const Counter = () => {
const [count, setCount] = useState(0);
const counterTimeout = useRef(null);
const updateCount = () => {
setCount(count => count + 1);
counterTimeout.current = window.setTimeout(updateCount, 500);
};
useEffect(() => {
console.log("count", count);
}, [count]);
useEffect(() => {
updateCount();
return () => {
window.clearTimeout(counterTimeout.current);
};
}, []);
return null;
};
Для завершения здесь мой вариант с тем же подходом но используя setInterval
. Из-за этого интервал обработки только внутри useEffect
, поэтому ссылка не требуется. Он также имеет небольшую оптимизацию с использованием useCallback
для updateCount
.
const CounterInterval = () => {
const [count, setCount] = useState(0);
const updateCount = useCallback(() => {
setCount(count => count + 1);
}, [setCount]);
useEffect(() => {
console.log("interval count", count);
}, [count]);
useEffect(() => {
const counterTimeout = window.setInterval(updateCount, 500);
return () => {
window.clearInterval(counterTimeout);
};
}, []);
return null;
};