除了在后台保留一个“时钟”以使用react钩子在轮播中实现自动下一步(几秒钟后)之外,还有其他选择吗?
下面的自定义react挂钩实现了轮播的状态,该轮播支持手动(下一步,上一步,重置)和自动(启动,停止)方法来更改轮播的当前(活动)索引。
const useCarousel = (items = []) => {
const [current, setCurrent] = useState(
items && items.length > 0 ? 0 : undefined
);
const [auto, setAuto] = useState(false);
const next = () => setCurrent((current + 1) % items.length);
const prev = () => setCurrent(current ? current - 1 : items.length - 1);
const reset = () => setCurrent(0);
const start = _ => setAuto(true);
const stop = _ => setAuto(false);
useEffect(() => {
const interval = setInterval(_ => {
if (auto) {
next();
} else {
// do nothing
}
}, 3000);
return _ => clearInterval(interval);
});
return {
current,
next,
prev,
reset,
start,
stop
};
};
答案 0 :(得分:3)
setInterval
和setTimeout
之间存在一些差异,您可能不希望在重新渲染组件时始终重新启动计时器来丢失这些差异。 This fiddle显示了两者之间的漂移差异(并且甚至没有考虑到React正在执行的所有计算,这可能会使整个过程更进一步)。
现在请参考your answer,Marco,setInterval
的使用完全消失了,因为每次组件重新渲染时,无条件的效果都会处理并重新运行。因此,在您的第一个示例中,current
依赖项的使用导致该效果在current
每次更改时(每次运行间隔)都被释放并重新运行。第二个功能执行相同的操作,但实际上每次状态更改时(导致重新渲染),这都可能导致某些意外行为。一个可行的唯一原因是因为next()
导致状态更改。
考虑到您可能不关心精确时间的事实,最简单的方法是使用setTimeout
和{{1} } vars作为依赖项。因此,要重新陈述部分答案,请执行以下操作:
current
请注意,我相信您可以将
auto
的依赖项替换为useEffect( () => { if (!auto) return; const interval = setInterval(_ => { next(); }, autoInterval); return _ => clearInterval(interval); }, [auto, current] );
,因为它可以更直接地表示您在current
内部所做的事情。但是我不是100%知道React如何区分这些依赖关系,所以我将其保留。
通常来说,对于那些刚刚阅读此答案并想要做一个简单计时器的方法,此版本不考虑OP的原始代码,也不考虑独立启动和停止计时器的方法:
next
但是,考虑到useEffect
的漂移可能大于const [counter, setCounter] = useState(0);
useEffect(
() => {
const id= setTimeout(() => {
setCounter(counter + 1);
}, 1000);
return () => {
clearTimer(id);
};
},
[counter],
);
的事实,您可能想知道如何使用更精确的间隔。再次,这是一种不使用OP的代码的通用方法:
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'],
);
当前可接受的版本,我们需要一些可变的状态。 React用setInterval
给了我们。 setCounter
函数将返回一个具有useRef
属性的对象。然后,我们可以将该属性(每次重新渲染组件时都会发生)设置为useRef
和current
的当前版本。
然后,为了防止间隔在每个渲染器上被丢弃,我们向counter
添加了一个依赖关系,该依赖关系保证永远不变。就我而言,我喜欢使用字符串setCounter
来指示我仅将此效果设置为一次。卸下组件后,该间隔仍将被丢弃。
因此,将我们所了解的内容应用于OP的原始问题,您可以使用useEffect
进行这样不太容易漂移的幻灯片演示:
"once"
答案 1 :(得分:1)
因为只要{em>应该运行,current
的值就会在每个“间隔”中更改,因此您的代码将在每个渲染器上启动和停止新的计时器。您可以在这里看到实际效果:
https://codesandbox.io/s/03xkkyj19w
您可以将setInterval
更改为setTimeout
,您将获得完全相同的行为。 setTimeout
并不是一个永久性的时钟,但是没关系,因为它们都会被清理。
如果您根本不想启动任何计时器,请将该条件放在setInterval
之前而不是其中。
useEffect(
() => {
let id;
if (run) {
id = setInterval(() => {
setValue(value + 1)
}, 1000);
}
return () => {
if (id) {
alert(id) // notice this runs on every render and is different every time
clearInterval(id);
}
};
}
);
答案 2 :(得分:0)
到目前为止,似乎以下两种解决方案都可以正常工作:
有条件地创建计时器 –它要求useEffect依赖于auto
和current
才能起作用
useEffect(
() => {
if (!auto) return;
const interval = setInterval(_ => {
next();
}, autoInterval);
return _ => clearInterval(interval);
},
[auto, current]
);
有条件地执行状态更新-不需要useEffect依赖项
useEffect(() => {
const interval = setInterval(_ => {
if (auto) {
next();
} else {
// do nothing
}
}, autoInterval);
return _ => clearInterval(interval);
});
如果我们将setInterval
替换为setTimeout
,则这两种解决方案都有效
答案 3 :(得分:0)
您可以使用useTimeout
挂钩,该挂钩在指定的毫秒数后返回true
。