当前位置: 首页 > 科技观察

在使用React-Hooks时,需要注意过时的闭包!

时间:2023-03-20 14:42:18 科技观察

本文已获得原作者Shadeed授权翻译。Hooks简化了React组件的内部状态和副作用的管理。此外,可以将重复的逻辑提取到自定义Hook中,以便在整个应用程序中重复使用。Hooks严重依赖JS闭包。这就是为什么Hooks如此富有表现力和简单,而Closures有时却很棘手。使用Hooks时可能会遇到的一个问题是陈旧的闭包,这可能很难解决。让我们从过时的装饰开始。然后,看看过时的闭包如何影响ReactHooks,以及如何修复它。1.过时的闭包工厂函数createIncrement(incBy)返回增量和日志函数的元组。调用时,increment()函数将内部值递增incBy,而log()只是打印一条消息,其中包含有关当前值的信息:functioncreateIncrement(incBy){letvalue=0;functionincrement(){value+=incBy;console.log(值);}constmessage=`Currentvalueis${value}`;functionlog(){console.log(message);}return[increment,log];}const[increment,log]=createIncrement(1);increment();//1increment();//2increment();//3//不能正常工作!log();//"Currentvalueis0"[increment,log]=createIncrement(1)返回一个函数元组:一个函数增加内部值,另一个函数记录当前值。然后,3次调用increment()将值递增到3。最后,log()调用打印消息当前值为0,这有点出乎意料,因为现在值是3。log()是一个过时的闭包。闭包log()捕获值为“当前值为0”的消息变量。即使在调用increment()时value变量被递增多次,message变量也不会更新并且始终持有一个过时的值“当前值为0”。过时的闭包捕获具有过时值的变量。2.修复过时的闭包要修复过时的log()问题,需要关闭实际改变的变量:value的闭包。我们移动语句constmessage=...;在log()函数内部:functioncreateIncrement(incBy){letvalue=0;functionincrement(){value+=incBy;console.log(value);}functionlog(){constmessage=`Currentvalueis${value}`;console.log(消息);}return[increment,log];}const[increment,log]=createIncrement(1);increment();//1increment();//2increment();//3//Works!log();//"Currentvalueis3"现在调用increment()函数3次后,调用log()记录实际值:"Currentvalueis3"。3.Hooks中过时的闭包3.1useEffect()让我们看一下使用useEffect()过时闭包的常见情况。组件中useEffect()每2秒记录一次count的值functionWatchCount(){const[count,setCount]=useState(0);useEffect(function(){setInterval(functionlog(){console.log(`Countis:${count}`);},2000);},[]);return(

{count}setCount(count+1)}>增加
);}打开示例(https://codesandbox.io/s/stale-closure-use-effect-broken-2-gyhzk)并点击添加按钮几次。然后查看控制台,Countis:0每2秒出现一次,尽管count状态变量实际上已经递增了几次。为什么会这样?在第一次渲染时,状态变量count被初始化为0。组件挂载后,useEffect()调用setInterval(log,2000)定时器函数,该函数被安排为每2秒调用log()函数。在这里,闭包log()捕获了count变量为0。之后,即使单击Increase按钮??时计数增加,定时器函数每2秒调用一次log(),使用count的值仍然为0.log()成为过时的闭包。解决方法是让useEffect()知道闭包log()依赖于count,正确处理count变化时间隔的重置functionWatchCount(){const[count,setCount]=useState(0);useEffect(function(){constid=setInterval(functionlog(){console.log(`Countis:${count}`);},2000);returnfunction(){clearInterval(id);}},[count]);return(
{count}setCount(count+1)}>Increase
);}正确设置依赖后,useEffect()会在count发生变化时更新闭包.3.2useState()组件有1个按钮,它以1秒的延迟异步增加计数器。functionDelayedCount(){const[count,setCount]=useState(0);functionhandleClickAsync(){setTimeout(functiondelay(){setCount(count+1);},1000);}return(
{count}Increaseasync
);}现在打开演示(https://codesandbox.io/s/use-state-broken-0q994)。快速点击2次按钮。计数器仅更新为1,而不是预期的2。每点击一次setTimeout(delay,1000)都会在1秒后执行delay()。此时delay()捕获的计数为0。两个delay()将状态更新为相同的值:setCount(count+1)=setCount(0+1)=setCount(1)。这是因为在第二次点击的delay()闭包中捕获了0的过时计数变量。为了解决这个问题,我们使用函数式方法setCount(count=>count+1)来更新计数状态functionDelayedCount(){const[count,setCount]=useState(0);functionhandleClickAsync(){setTimeout(functiondelay(){setCount(count=>count+1);},1000);}functionhandleClickSync(){setCount(count+1);}return(
{count}增加同步Increasesync
);}打开演示(https://codesandbox.io/s/use-state-fixed-zz78r)。再次快速单击该按钮2次。计数器显示正确的值2。当将基于先前状态返回新状态的回调函数提供给状态更新函数时,React确保将最新状态值作为参数提供给回调函数setCount(alwaysActualStateValue=>新状态值);这就是为什么在状态更新过程中出现的陈旧装饰问题可以通过函数这种方式来解决。4.总结当闭包捕获陈旧的变量时,就会出现陈旧的闭包问题。对于过时的闭包,一个有效的解决方案是正确设置Reacthooks的依赖关系。或者,在无效状态的情况下,使用功能方式更新状态。~完了,我是小智,我来洗碗了。作者:Shadeed译者:前端小智来源:dmitripavlutin原文:https://dmitripavlutin.com/react-hooks-stale-closures/本文转载自微信公众号「大运世界」,可以关注以下二维码。转载本文请联系大千世界公众号。