反应钩子和setInterval

时间:2018-12-30 21:33:42

标签: reactjs carousel setinterval react-hooks

除了在后台保留一个“时钟”以使用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
  };
};

4 个答案:

答案 0 :(得分:3)

setIntervalsetTimeout之间存在一些差异,您可能不希望在重新渲染组件时始终重新启动计时器来丢失这些差异。 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属性的对象。然后,我们可以将该属性(每次重新渲染组件时都会发生)设置为useRefcurrent的当前版本。

然后,为了防止间隔在每个渲染器上被丢弃,我们向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依赖于autocurrent才能起作用

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