反应钩子和 setInterval [英] react hooks and setInterval

查看:22
本文介绍了反应钩子和 setInterval的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

除了在后台保留一个时钟"以使用 React 钩子在轮播中实现自动下一步(几秒钟后)之外,还有其他选择吗?

Is there any alternative to just keeping a "clock" in the background to implement auto-next (after a few seconds) in carousel using react hooks?

下面的自定义反应钩子为轮播实现了一个状态,支持手动(next、prev、reset)和自动(start、stop)方法来改变轮播的当前(活动)索引.

The custom react hook below implements a state for a carousel that supports manual (next, prev, reset) and automatic (start, stop) methods for changing the carousel's current (active) index.

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
  };
};

推荐答案

setIntervalsetTimeout 之间存在一些差异,您可能不想因为总是重新启动计时器而丢失这些差异当组件重新渲染时.这个小提琴 显示了当其他代码也在运行时两者之间的漂移差异.(在较旧的浏览器/机器上——比如从我最初回答这个问题时开始——你甚至不需要模拟大型计算就可以看到仅仅几秒钟后就开始发生显着的漂移.)

There are differences between setInterval and setTimeout that you may not want to lose by always restarting your timer when the component re-renders. This fiddle shows the difference in drift between the two when other code is also running. (On older browsers/machines—like from when I originally answered this question—you don't even need to simulate a large calculation to see a significant drift begin to occur after only a few seconds.)

现在参考 您的答案,Marco,setInterval 的使用完全丢失,因为每次组件重新渲染时,无条件的效果都会处理并重新运行.因此,在您的第一个示例中,使用 current 依赖项会导致该效果在每次 current 更改时(每次间隔运行时)处理并重新运行.第二个做同样的事情,但实际上每次任何状态改变(导致重新渲染),这可能会导致一些意想不到的行为.一个工作的唯一原因是因为 next() 导致状态改变.

Referring now to your answer, Marco, the use of setInterval is totally lost because effects without conditions dispose and re-run every time the component re-renders. So in your first example, the use of the current dependency causes that effect to dispose and re-run every time the current changes (every time the interval runs). The second one does the same thing, but actually every time any state changes (causing a re-render), which could lead to some unexpected behavior. The only reason that one works is because next() causes a state change.

考虑到您可能不关心精确时间,以简单的方式使用setTimeout是最干净的,使用currentauto 变量作为依赖项.因此,要重新陈述您的部分答案,请执行以下操作:

Considering the fact that you are probably not concerned with exact timing, is is cleanest to use setTimeout in a simple fashion, using the current and auto vars as dependencies. So to re-state part of your answer, do this:

useEffect(
  () => {
    if (!auto) return;
    const interval = setTimeout(_ => {
      next();
    }, autoInterval);
    return _ => clearTimeout(interval);
  },
  [auto, current]
);

一般来说,对于那些只是阅读这个答案并想要一种方法来做一个简单的计时器的人,这里的版本没有考虑到 OP 的原始代码,也没有考虑到他们需要一种独立启动和停止计时器的方法:

Generically, for those just reading this answer and want a way to do a simple timer, here is a version that doesn't take into account the OP's original code, nor their need for a way to start and stop the timer independently:

const [counter, setCounter] = useState(0);
useEffect(
  () => {
    const id= setTimeout(() => {
      setCounter(counter + 1); 
      // You could also do `setCounter((count) => count + 1)` instead.
      // If you did that, then you wouldn't need the dependency
      // array argument to this `useEffect` call.
    }, 1000);
    return () => {
      clearTimeout(id);
    };
  },
  [counter],
);

然而,鉴于 setTimeoutsetInterval 漂移得更多,您可能想知道如何使用更精确的间隔.这是一种方法,同样,不使用 OP 代码的通用方法:

However, you may be wondering how to use a more exact interval, given the fact that setTimeout can drift more than setInterval. Here is one method, again, generic without using the OP's code:

// Using refs:

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);
    };
  },
  [] // empty dependency array
);

// Using the function version of `setCounter` is cleaner:

const [counter, setCounter] = useState(30);
useEffect(
  () => {
    const id = setInterval(() => {
      setCounter((count) => count + 1);
    }, 1000);
    return () => {
      clearInterval(id);
    };
  },
  [] // empty dependency array
);

这是上面发生的事情:

(第一个例子,使用 refs):为了让 setInterval 的回调总是引用当前可接受的 setCounter 版本,我们需要一些可变状态.React 通过 useRef 为我们提供了这一点.useRef 函数将返回一个具有 current 属性的对象.然后我们可以将该属性(每次组件重新渲染时都会发生)设置为 countersetCounter 的当前版本.

(first example, using refs): To get setInterval's callback to always refer to the currently acceptable version of setCounter we need some mutable state. React gives us this with useRef. The useRef function will return an object that has a current property. We can then set that property (which will happen every time the component re-renders) to the current versions of counter and setCounter.

(第二个例子,使用函数式setCounter):与第一个相同的想法,除了当我们使用setCounter的函数版本时,我们将可以访问计数的当前版本作为函数的第一个参数.无需使用 ref 来保持最新状态.

(second example, using functional setCounter): Same idea as the first, except that when we use the function version of setCounter, we will have access to the current version of the count as the first argument to the function. No need to use a ref to keep things up to date.

(两个例子,继续):然后,为了防止间隔在每次渲染时被处理,我们添加一个空的依赖数组作为 useEffect 的第二个参数.卸载组件时,间隔仍然会被清除.

(both examples, continued): Then, to keep the interval from being disposed of on each render, we add an empty dependency array as the second argument to useEffect. The interval will still be cleared when the component is unmounted.

注意:我曾经喜欢使用 ["once"] 作为我的依赖数组来表明我强制只设置一次这个效果.当时它的可读性很好,但我不再使用它有两个原因.首先,现在钩子得到了更广泛的理解,我们到处都看到了空数组.其次,它与非常流行的钩子规则"相冲突.linter 对依赖数组中的内容非常严格.

Note: I used to like using ["once"] as my dependency array to indicate that I am forcing this effect to be set up only once. It was nice for readability at the time, but I no longer use it for two reasons. First, hooks are more widely understood these days and we have seen the empty array all over the place. Second, it clashes with the very popular "rule of hooks" linter which is quite strict about what goes in the dependency array.

因此,将我们所知道的应用于 OP 的原始问题,您可以使用 setInterval 进行这样的不太可能漂移的幻灯片放映:

So applying what we know to the OP's original question, you could use setInterval for a less-likely-to-drift slideshow like this:

// ... 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]
);

这篇关于反应钩子和 setInterval的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆