function createIncrement(incBy) {
let value = 0;
function increment() {
value += incBy;
console.log(value);
}
const message = `Current value is ${value}`; function log() { console.log(message); }
return [increment, log];
}
const [increment, log] = createIncrement(1);
increment(); // 1
increment(); // 2
increment(); // 3
// 不能正确工作!
log(); // "Current value is 0"
[increment, log] = createIncrement(1)返回一个函数元组:一个函数增加内部值,另一个函数记录当前值。然后,increment()的3次调用将 value递增到3。最后,log()调用打印消息是 Current value is 0,这有点出乎意料的,因为此时 value 为 3 了。 " c' i! y( [4 E) `1 n9 _0 Y5 u log()是一个过时的闭包。闭包 log()捕获了值为 "Current value is 0"的 message 变量。即使 value 变量在调用increment()时被增加多次,message变量也不会更新,并且总是保持一个过时的值 "Current value is 0"。过时的闭包捕获具有过时值的变量。 * V& t& c( ]' F% `' C X* j- V' _2.修复过时的闭包3 r% S+ ~. z* b4 u
修复过时的log()问题需要关闭实际更改的变量:value的闭包。我们将语句 const message = ...; 移动到 log() 函数内部:& k: _0 B" j( E9 \0 g ~6 \" q- S
打开事例(https://codesandbox.io/s/stale-closure-use-effect-broken-2-gyhzk)并点击几次增加按钮。然后看看控制台,每2秒出现一次Count is: 0,尽管count状态变量实际上已经增加了几次。- Q. J+ Q7 E5 C5 ~. h) Z/ Y9 m : g% L, s! l6 w! G3 c 为什么会这样?第一次渲染时,状态变量count初始化为0。组件安装后,useEffect()调用 setInterval(log, 2000)计时器函数,该计时器函数计划每2秒调用一次log()函数。在这里,闭包log()捕获到count变量为0。$ D' }" c7 m3 b- w
之后,即使在单击Increase按钮时count增加,计时器函数每2秒调用一次的log(),使用count的值仍然是0。log()成为一个过时的闭包。 ! V% S% c ?1 a' m; {& A7 C+ P7 F- y* Z 解决方案是让useEffect()知道闭包log()依赖于count,并在count改变时正确处理间隔的重置: - q1 Y9 v. `2 R. K! E- \, g
打开演示(https://codesandbox.io/s/use-state-fixed-zz78r)。再次快速单击按钮2次。计数器显示正确的值2。- F2 R: T5 J7 o$ r. j. j% v
当一个返回基于前一个状态的新状态的回调函数被提供给状态更新函数时,React确保将最新的状态值作为该回调函数的参数提供:2 g J: V3 Q" d' Y$ N; u/ B+ L
这就是为什么在状态更新过程中出现的过时装饰问题可以通过函数这种方式来解决。6 e8 M5 i( _* t( I. S2 C 4.总结( p+ _+ d5 ~" K' T* V
当闭包捕获过时的变量时,就会发生过时的闭包问题。解决过时闭包的有效方法是正确设置React钩子的依赖项。或者,在失效状态的情况下,使用函数方式更新状态。 ! a* r! c' F& ?- E. ]3 {7 N . a9 @* l$ z6 y4 v