在React中编写useInterval钩子

理解React hooks可能一开始会让人望而生畏,特别是如果你遇到与时间相关的问题,比如setInterval()。为了解决这类问题,你必须熟悉hooks的工作方式、它们的限制以及潜在的解决方法。

首先,应该明确setInterval()是一个副作用。毕竟,它不直接与组件的渲染方法相关联。因此,我们应该在useEffect()钩子内调用它,并使用其return来在卸载时调用clearInterval()。为了避免创建多个间隔,我们可以使用钩子的第二个参数来传递一个空的依赖数组([])。这样只有在组件挂载时才会运行副作用。

React.useEffect(() => {
  let id = setInterval(callback, delay);
  return () => clearInterval(id);
}, []);

setInterval()内部的闭包只能访问在其实例化时可用的变量和值。这意味着我们必须特别注意其第一个参数,以确保每次间隔运行时都有新的值可用。解决这个问题的最简单方法是创建一个被React视为可变的变量,使用useRef()钩子。这样我们就可以在需要时访问新的值。

const savedCallback = React.useRef(callback);

React.useEffect(() => {
  let id = setInterval(savedCallback.current, delay);
  return () => clearInterval(id);
}, []);

使用useRef()钩子可能只是转移了问题。现在,创建的ref的值需要在setInterval()内部刷新。幸运的是,这是一个容易解决的问题。我们可以创建一个包装函数,将函数传递给setInterval()。这样,传递给setInterval()的函数将永远不会改变,但封闭的ref的值在调用时将始终是最新的。

const savedCallback = React.useRef(callback);

React.useEffect(() => {
  function tick() {
    savedCallback.current();
  }
  let id = setInterval(tick, delay);
  return () => clearInterval(id);
}, []);

最后,让我们将所有内容提取到一个自定义的hook中,以便重用。我们可以将callback作为自定义hook的参数提取出来,并将其作为额外的useEffect() hook的唯一依赖项,用于更新回调函数的ref。

const useInterval = callback => {
  const savedCallback = React.useRef();

  React.useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  React.useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, []);
};

就是这样了。稍加努力,我们可以将delay添加到自定义hook的参数中,并得到一个完整的setInterval()的hook版本。你可以在useInterval代码片段中找到这个最终调整的hook的实现,以及一些使用示例。