Существуют различия между setInterval
и setTimeout
, которые вы, возможно, не захотите потерять, всегда перезапуская таймер при повторной визуализации компонента. Эта скрипка показывает разницу в смещении между двумя (и это даже не учитывает все вычисления, которые выполняет React, которые могут отбросить все это немного дальше).
Обращаясь теперь к вашему ответу , Марко, использование setInterval
полностью потеряно, потому что эффекты без условий располагаются и перезапускаются каждый раз, когда компонент перерисовывает.Итак, в вашем первом примере использование зависимости current
приводит к тому, что этот эффект удаляется и перезапускается каждый раз, когда изменяется current
(каждый раз, когда выполняется интервал).Второй делает то же самое, но фактически каждый раз, когда изменяется любое состояние (вызывая повторный рендеринг), что может привести к неожиданному поведению.Единственная причина, по которой он работает, заключается в том, что next()
вызывает изменение состояния.
Учитывая тот факт, что вы, вероятно, не имеете отношения к точному времени, лучше всего использовать setTimeout
впростой способ, используя переменные current
и auto
в качестве зависимостей.Итак, чтобы перефразировать часть вашего ответа, сделайте следующее:
useEffect(
() => {
if (!auto) return;
const interval = setInterval(_ => {
next();
}, autoInterval);
return _ => clearInterval(interval);
},
[auto, current]
);
Обратите внимание, что я считаю, что вы можете заменить зависимость current
на next
, так как это более прямо представляет васделать внутри useEffect
.Но я не уверен на 100%, как React различает эти зависимости, поэтому я оставляю все как есть.
Обычно для тех, кто просто читает этот ответ и хочет получить простой таймер, вотверсия, в которой не учитывается исходный код OP, а также необходимость в способе запуска и остановки таймера независимо:
const [counter, setCounter] = useState(0);
useEffect(
() => {
const id= setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimer(id);
};
},
[counter],
);
Однако вам может быть интересно, как использовать более точный интервал, учитывая тот факт, что setTimeout
может дрейфовать больше, чем setInterval
.Вот еще один метод, универсальный, без использования кода ОП:
const [counter, setCounter] = useState(30);
const r = useRef(null);
r.current = { counter, setCounter };
useEffect(
() => {
const id = setInterval(() => {
r.current.setCounter(r.current.counter + 1);
}, 1000);
return () => {
clearInterval(id);
};
},
['once'],
);
Что здесь происходит?Ну, чтобы обратный вызов setInterval
всегда ссылался на приемлемую на данный момент версию setCounter
, нам нужно некоторое изменяемое состояние.Реакт дает нам это с useRef
.Функция useRef
вернет объект, обладающий свойством current
.Затем мы можем установить это свойство (которое будет происходить каждый раз при повторном рендеринге компонента) для текущих версий counter
и setCounter
.
Затем, чтобы предотвратить удаление интервала при каждом рендеринге.мы добавляем зависимость к useEffect
, которая гарантированно никогда не изменится.В моем случае мне нравится использовать строку "once"
, чтобы указать, что я заставляю этот эффект быть установленным только один раз.Интервал будет по-прежнему удаляться, когда компонент отключен.
Таким образом, применяя то, что мы знаем к исходному вопросу ОП, вы можете использовать setInterval
для слайд-шоу с меньшей вероятностью дрейфа, подобного этому:
// ... OP's implementation code including `autoInterval`,
// `auto`, and `next` goes above here ...
const r = useRef(null);
r.current = { next };
useEffect(
() => {
if (!auto) return;
const id = setInterval(() => {
r.current.next();
}, autoInterval);
return () => {
clearInterval(id);
};
},
[auto],
);