React useCallback闭包变量读取机制及示例中cb2重渲染后输出结果的技术问询
嘿,这个问题问到了React里闭包和useCallback结合时最容易踩的坑点,我给你一步步拆解清楚:
首先得搞懂两个核心点:useCallback的缓存逻辑,以及React组件渲染时的作用域隔离。
1. useCallback的缓存规则
useCallback的作用是根据依赖数组来决定是否返回新的函数引用:
- 当依赖数组是空
[]时,只有组件第一次渲染时会创建这个函数实例,之后不管组件怎么重渲染,只要依赖没变化(空数组永远不会变),就一直复用第一次创建的那个函数。 - 你的示例里
cb2就是这种情况,它从第一次渲染后就再也没被重新创建过。
2. 闭包与渲染作用域的绑定
React组件每次重渲染,都会重新执行一遍你的TestHook函数,每个渲染周期都有完全独立的函数作用域,里面的变量都是新声明的。而useCallback缓存的函数,会牢牢绑定它创建时那个渲染周期的作用域——也就是第一次渲染的作用域。
现在对应你的示例里cb2的输出,拆分两个变量的不同表现:
关于count(useState状态变量)
count是useState返回的状态变量,每个渲染周期的count都是当前状态的快照——简单说就是,第一次渲染时count是0,重渲染时新的作用域里count是1,这是两个完全独立的快照值。
但cb2是第一次渲染时创建的,它的闭包只能捕获第一次渲染时的count快照,所以不管之后怎么重渲染,cb2里的count永远是0。
关于countVal(普通let变量)
countVal是你在TestHook内部声明的普通变量,每次重渲染都会重新声明一个新的countVal(初始值0)。但这里有个关键细节:
你点击add按钮时,执行的是第一次渲染时创建的add函数——这个函数绑定的是第一次渲染作用域里的countVal。当你执行countVal++时,修改的是第一次渲染作用域里的那个countVal,而cb2刚好绑定的也是这个作用域的countVal引用,所以当cb2在重渲染后执行时,读取到的是已经被修改成1的那个变量值。
而重渲染时新声明的countVal=0是另一个完全独立的变量,和cb2没有任何关系。
对应示例的最终结果解释
当你点击一次add按钮后:
add函数修改第一次渲染作用域里的countVal为1,然后setCount触发重渲染。- 重渲染时,组件执行新的
TestHook,创建新的count(值1)、新的countVal(值0),但cb2还是第一次渲染的那个函数。 - 此时执行
cb2(),它会输出:count:第一次渲染的快照值0countVal:第一次渲染作用域里被修改后的1
这就是你看到cb2()第二次输出0, 1的原因啦!
备注:内容来源于stack exchange,提问作者2tuzi

