React 中的 setTimeout 清除器 hook 实现

2,629 阅读2分钟

在工作中使用到 setTimeout 定时器时,一般不需要去担心清除定时器。但如果在使用 React 开发时,去会出现一些讨厌的边界案例。

如果我们想在某段时间后操作数据,但在这个时间节点之前组件也许已经被卸载了,但是定时器设置的函数还在尝试运行。这也就触发了一些操作被还原的边界案例,也有可能在控制台中得到一些内存泄漏的信息。

清除定时器

一般的建议就是要跟踪在代码中创建的定时器并清理。在 React 中,组件的卸载需要涉及生命周期,对于类组件时 componentWillUnmount,而对于函数组件则是利用 useEffect,如果 useEffect 返回一个函数,React 将会在执行清除操作时调用它。

一个简单的案例代码如下:

export default function Test() {
  const [show, setShow] = useState(false);
  useEffect(() => {
    const test = window.setTimeout(() => {
      setShow(false);
    }, 1500);
    return () => {
      clearInterval(test);
    };
  }, []);

  return (
    <div>
      <h1>Loading...</h1>
      {show && <p>I'm fully loaded now</p>}
    </div>
  );
}

使用 ref 来清除定时器也是不错的选择

const timeoutRef = useRef();

useEffect(() => {
  timeoutRef.current = window.setTimeout(() => {
    setShow(false);
  }, 1500);
  return () => clearInterval(timeoutRef.current);
}, []);

这样既可以实现在 React 组件卸载时清除定时器。不过每次都要想着清除或许有些麻烦,不妨创建一个 hook 来实现 React 中的定时器以及卸载时清除。

useTimeout

我们可以引入一个 useTimeout Hook,这个 Hook 应该有以下 options。

  • 接收回调函数(超时后应该发生的动作)callback
  • 接收延迟(超时的时间)delay
  • 返回一个可以被调用的函数来启动它 return
import { useCallback, useEffect, useRef, useMemo } from 'react';

export default function useTimeout(callback, delay) {
  const timeoutRef = useRef();
  const callbackRef = useRef(callback);

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

  useEffect(() => {
    return () => window.clearTimeout(timeoutRef.current);
  }, []);

  const memoizedCallback = useCallback(
    (args) => {
      if (timeoutRef.current) {
        window.clearTimeout(timeoutRef.current);
      }
      timeoutRef.current = window.setTimeout(() => {
        timeoutRef.current = null;
        callbackRef.current?.(args);
      }, delay);
    },
    [delay, timeoutRef, callbackRef]
  );

  return useMemo(() => memoizedCallback, [memoizedCallback]);
}

  • 首先,传递的参数是回调 callback 和延迟时间 delay。然后在 Hook 中添加两个 ref 来跟踪定时器和回调函数。

  • 然后我们需要两个useEffects,一个用来监听回调,以防它在渲染后发生变化(回调内改变了任何状态时)。第二个是用来处理超时的清理效果(当组件被卸载时)。

  • 然后创建一个 useCallback,在其中首先清除 ref 中的任何现有定时器,然后分配新定时器。这整个 useCallback 函数会监听所有变量的变化,保证不会过度渲染。

  • 最后一部分是返回一个记忆化的函数,并监听其回调的变化。

这可能看起来有些矫枉过正,不过也是锻炼一下对于 setTiemout 以及 hook 的理解,让定时器清除更加彻底。

参考原文地址:h.daily-dev-tips.com/react-clean…