看这样一个组件,通过定时器不断的累加 count:
            
            
              js
              
              
            
          
          import { useEffect, useState } from 'react';
function App() {
    const [count,setCount] = useState(0);
    useEffect(() => {
        setInterval(() => {
            console.log(count);
            setCount(count + 1);
        }, 1000);
    }, []);
    return <div>{count}</div>
}
export default App;
        你觉得这个 count 会每秒加 1 么? 不会。
可以看到,setCount 时拿到的 count 一直是 0。
为什么呢?
大家可能觉得,每次渲染都引用最新的 count,然后加 1,所以觉得没问题:

但是,现在 useEffect 的依赖数组是 [],也就是只会执行并保留第一次的 function。
而第一次的 function 引用了第一次渲染的 count,当第二次渲染的时候,取的count还是第一次function里面的count,所以形成了闭包,使用了自由变量(不是在自己函数内部定义的变量)就是闭包。
也就是实际上的执行是这样的:

这就导致了每次执行定时器的时候,都是在 count = 0 的基础上加一。
这就叫做 hook 的闭包陷阱。
那怎么解决这个问题呢?不让它形成闭包不就行了?
这时候可以用 setState 的另一种参数:
            
            
              js
              
              
            
          
          useEffect(() => {
        setInterval(() => {
            console.log(count);
            setCount(count => count + 1);
        }, 1000);
    }, []);
        这次并没有形成闭包,每次的 count 都是参数传入的上一次的 state。
但有的时候,是必须要用到 state 的,也就是肯定会形成闭包,
比如这里,console.log 的 count 就用到了外面的 count,形成了闭包,但又不能把它挪到 setState 里去写:
            
            
              js
              
              
            
          
          useEffect(() => {
    setInterval(() => {
        console.log(count);
        setCount(count => count + 1);
    }, 1000);
}, []);
        这种情况怎么办呢?
还记得 useEffect 的依赖数组是干啥的么?当依赖变动的时候,会重新执行 effect。
所以可以这样:
            
            
              js
              
              
            
          
          import { useEffect, useState } from 'react';
function App() {
    const [count,setCount] = useState(0);
    useEffect(() => {
        console.log(count);
        const timer = setInterval(() => {
            setCount(count + 1);
        }, 1000);
        return () => {
            clearInterval(timer);
        }
    }, [count]);
    return <div>{count}</div>
}
export default App;
        依赖数组加上了 count,这样 count 变化的时候重新执行 effect,那执行的函数引用的就是最新的 count 值。
但是,有定时器不能重新跑 effect 函数,那怎么做呢?
可以用 useRef。
            
            
              js
              
              
            
          
          import { useEffect, useState, useRef, useLayoutEffect } from 'react';
function App() {
    const [count, setCount] = useState(0);
    const updateCount = () => {
        setCount(count + 1);
    };
    const ref = useRef(updateCount);
    ref.current = updateCount;
    useEffect(() => {
        const timer = setInterval(() => ref.current(), 1000);
        return () => {
            clearInterval(timer);
        }
    }, []);
    return <div>{count}</div>;
}
export default App;
        通过 useRef 创建 ref 对象,保存执行的函数,每次渲染更新 ref.current 的值为最新函数。
这样,定时器执行的函数里就始终引用的是最新的 count。
useEffect 只跑一次,保证 setIntervel 不会重置,是每秒执行一次。
执行的函数是从 ref.current 取的,这个函数每次渲染都会更新,引用着最新的 count。
讲 useRef 的时候说过,ref.current 的值改了不会触发重新渲染,
它就很适合这种保存渲染过程中的一些数据的场景。
再来看一个例子:
            
            
              js
              
              
            
          
          import { type FC, useState, useRef } from 'react'
const Demo: FC = () => {
  const [count, setCount] = useState(0)
  const add = () => {
    setCount(count + 1)
  }
  const alertFn = () => {
    setTimeout(() => {
      alert(count)
    }, 3000)
  }
  return (
    <div>
      <p>闭包陷阱</p>
      <div>
        <p>{count}</p>
        <button onClick={add}>add</button>
        <button onClick={alertFn}>alert</button>
      </div>
    </div>
  )
}
export default Demo
        当你先点击alertFn函数,然后快速点击add函数,此时 alert(count) 中的仍然是初始值0,这也是因为闭包导致的。
怎么做呢?仍然是用useRef来解决:
            
            
              js
              
              
            
          
          import { type FC, useState, useRef } from 'react'
const Demo: FC = () => {
  const [count, setCount] = useState(0)
  const countRef = useRef(0)
  console.log('count改变时都会执行', count)
  countRef.current = count
  const add = () => {
    setCount(count + 1)
  }
  const alertFn = () => {
    setTimeout(() => {
      // alert(count)
      alert(countRef.current)
    }, 3000)
  }
  return (
    <div>
      <p>闭包陷阱</p>
      <div>
        <p>{count}</p>
        <button onClick={add}>add</button>
        <button onClick={alertFn}>alert</button>
      </div>
    </div>
  )
}
export default Demo
        总结
闭包陷阱指的是在 React 组件中,由于闭包特性捕获了当前作用域的 state/prop ,导致在后续操作(如异步函数、定时器)中访问到的仍是旧值,而非最新状态。
原因:
- React 函数组件的执行机制
 
函数组件每次渲染时,都会生成一个全新的函数作用域,其中的 state、prop 都是该次渲染的 "快照"。
React 的 state 是不可变的,setState(或 setXxx)并不会修改当前 state,而是触发新的渲染并生成新的 state。
- 闭包对作用域的捕获
 
当在组件中定义回调函数(如事件处理、定时器、异步操作)时,函数会捕获当前渲染作用域中的 state/prop。即使后续状态更新触发了新的渲染,旧的回调函数仍持有对旧作用域中变量的引用。
闭包一般会导致内存泄漏,但是react中异步函数访问旧的 state 本身不会直接导致内存泄漏。这是因为异步函数执行完毕后,若没有其他引用指向这个闭包,它会被垃圾回收机制清理。旧的 state 本身是不可变的快照,即使被闭包引用,只要不再被使用,最终也会被回收。