React中出现Stale Closure问题,无法理解成因求解
1. 闭包的渲染快照捕获特性
React组件每次渲染时,会创建当前状态/ props的快照,组件内定义的函数会捕获这个快照里的值。你的updateDesc函数在每次父组件渲染时,都会捕获当时的view值。
当你先修改标题时,父组件会更新view并重新渲染,此时会生成新的updateDesc函数(哪怕不用useCallback),这个新函数捕获的是更新后的view。但问题出在子组件MyComponent没有拿到这个新的函数引用。
2. 子组件的props更新阻塞
你给MyComponent传递的onBlur是一个匿名函数:
onBlur={text => updateDesc(text)}
如果MyComponent被React.memo包裹(或者内部实现了类似的浅比较优化),React会对比前后两次的onBlur prop:由于每次父组件渲染都会创建一个新的匿名函数,若MyComponent的浅比较逻辑没有及时识别到回调更新,或者内部缓存了旧的回调引用,就会导致子组件不重新渲染,仍然持有旧的onBlur函数。
这个旧的onBlur函数关联的是旧的updateDesc,而旧updateDesc捕获的是修改标题前的view值。当你修改描述触发onBlur时,就会用旧的view(包含旧标题)合并新的描述,最终导致标题回退。
3. 为什么移除useCallback没用?
不用useCallback时,父组件每次渲染都会生成新的updateDesc,但只要MyComponent没有更新onBlur prop,触发时调用的还是旧的updateDesc——它依然是捕获了旧view的闭包,问题自然不会解决。
4. ref解决问题的原因
ref的.current属性是可变的,不受React渲染快照的限制。当你把view存入ref后,每次updateDesc调用时都会直接读取.current的最新值,不管这个函数是何时创建的,自然不会再出现旧值捕获的问题。
内容的提问来源于stack exchange,提问作者user27985042

