千早爱音

react中useEffect的依赖陷阱

18, May, 2025
react

陷阱说明

众所周知, React的useEffect中必须要将状态放入依赖数组中才能在useEffect中拿到更新后的状态。

useEffect(() => {
  console.log(count)
},[count])

那如果这个useEffect是用来绑定事件或者启动定时器的,而且这个事件或者定时器的回调函数需要用到最新的状态,这样做会导致事件的重复绑定或者定时器的重复启动,显然是有问题的。

useEffect(() => {
  const interval = setInterval(() => {
    console.log(count) 
  },1000)
  return () => clearInterval(interval)
},[count])
// 每一次count更新都会再重新开启一个定时器

解决方案

这种情况的解决方案是回调函数ref化

  1. 将需要获取最新状态值的那部分代码抽取出来
  2. 定义一个ref存储这部分代码
  3. 每次更新时同时更新ref存储的代码
  4. 在回调函数中执行这个ref存储的代码
const callbackFn = useCallback(() => {
    console.log(count);
    setCount(pre => pre + 1)
}, [count]);
const callbackFnRef = useRef(callbackFn);
callbackFnRef.current = callbackFn;
    
useEffect(() => {
    const interval = setInterval(
        () => callbackFnRef.current()
        , 1000)
    return () => clearInterval(interval);
}, [])

原因是ref不参与react的状态系统,所以我们可以通过手动更新ref,从而避免useEffect依赖状态,解决这些问题。

封装hooks

在ahooks和usehooks库中已经把这类问题的解决方案封装成了hooks,理解了思路也是蛮简单的。

function useInterval(fn: Function, delay?: number | null) {
    const callbackFn = useRef(fn);

    useLayoutEffect(() => {
        callbackFn.current = fn;
    });
    
    useEffect(() => {
        const timer = setInterval(
            () => callbackFn.current(),
             delay || 0);

        return () => clearInterval(timer);
    }, []);
}