反应钩子和setInterval

mar*_*ves 6 setinterval carousel reactjs 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
  };
};
Run Code Online (Sandbox Code Playgroud)

Don*_*Don 12

当组件重新渲染时,您可能不希望通过始终重新启动计时器来丢失setInterval和之间的差异setTimeout这个小提琴显示了当其他代码也在运行时两者之间的漂移差异。(在较旧的浏览器/机器上——就像我最初回答这个问题时一样——你甚至不需要模拟大型计算就可以看到仅仅几秒钟后就开始发生显着的漂移。)

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

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

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

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

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

但是,鉴于setTimeout可以漂移超过setInterval. 这是一种方法,同样,不使用 OP 代码的通用方法:

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],
);
Run Code Online (Sandbox Code Playgroud)

这是上面发生的事情:

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

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

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

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

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

// 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
);
Run Code Online (Sandbox Code Playgroud)


azi*_*ium 1

因为current只要它应该运行,该值就会在每个“间隔”发生变化,所以您的代码将在每次渲染时启动和停止一个新的计时器。您可以在这里看到它的实际效果:

https://codesandbox.io/s/03xkkyj19w

你可以改变setIntervalsetTimeout,你会得到完全相同的行为。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);
        }
      };
    }
  );
Run Code Online (Sandbox Code Playgroud)