当前位置: 首页 > Web前端 > JavaScript

React官方团队出手弥补原生Hook的不足

时间:2023-03-27 01:23:08 JavaScript

大家好,我是Kason。我们知道在使用Hooks时存在所谓的闭包陷阱,考虑以下代码:functionChat(){const[text,setText]=useState('');constonClick=useCallback(()=>{sendMessage(text);},[]);return;}我们希望sendMessage在点击后传递最新的文本值。但实际上,由于回调函数被useCallback缓存起来,形成了一个闭包,所以点击的效果始终是sendMessage('')。这就是闭包陷阱。上述代码的一种解决方案是为useCallback添加依赖项:constonClick=useCallback(()=>{sendMessage(text);},[text]);但是这样做之后,每当依赖项(文本)发生变化时,useCallback将返回一个全新的onClick引用,从而失去了useCallback缓存函数引用的作用。闭包陷阱的出现,提高了Hooks的上手门槛,也让开发者更容易写出有bug的代码。现在,React官方团队要解决这个问题。欢迎加入人类优质前端框架群。解决usingEvent的方法是引入一个新的原生Hook——useEvent。它用于定义一个函数。这个函数有两个特点:在多次渲染组件时保持引用一致。函数中总能获取到最新的道具和状态。上面的示例使用useEvent进行了修改:functionChat(){const[text,setText]=useState('');constonClick=useEvent(()=>{sendMessage(text);});return;}当Chat组件多次渲染时,onClick总是指向同一个引用。并且在触发onClick的时候总能获取到text的最新值。之所以叫useEvent,是因为React团队认为这个Hook的主要应用场景是:封装事件处理函数。useEvent的实现并不难,代码类似下面:functionuseEvent(handler){consthandlerRef=useRef(null);//视图渲染后,更新`handlerRef.current`指向useLayoutEffect(()=>{handlerRef.current=handler;});//用useCallback包装,使得渲染时返回的函数引用一致returnuseCallback((...args)=>{constfn=handlerRef.current;returnfn(...args);},[]);}整体包括两部分:返回一个没有依赖的useCallback,这样每次渲染时函数的引用都是一致的useCallback((...args)=>{constfn=handlerRef.current;returnfn(...args);},[]);适时更新handlerRef.current,让实际执行的函数永远是最新的reference与开源Hooks的区别很多开源的Hooks库都实现了类似的功能(比如ahooks中的useMemoizedFn)useEvent和这些开源实现的区别主要体现在:useEvent定位于处理事件回调函数的单一场景,而useMemoizedFn定位于缓存各种函数。那么问题来了,既然功能相似,为什么useEvent会限制它的使用场景呢?答案是:为了更稳定。useEvent能否获取到最新的state和props,取决于handlerRef.current更新的时机。在上面的模拟实现中,usingEvent更新handlerRef.current的逻辑放在了useLayoutEffect回调中。这确保handlerRef.current在视图完成渲染后始终更新:useLayoutEffect(()=>{handlerRef.current=handler;});而事件回调触发的时机显然是在view渲染完成之后,所以可以稳定的获取到最新的state和props。注:源码中实际更新时间更早,但不影响这里的结论。我们来看看ahooks中的useMemoizedFn。fnRef.current的更新时机是useMemoizedFn执行时(即组件渲染时)://更新fnRef.currentfnRef.current=useMemo(()=>fn,[fn]);//...省略代码}当React18启用并发更新后,组件渲染的次数和时机是不确定的。所以useMemoizedFn中fnRef.current的更新时机也是不确定的。这会增加在并发更新下使用时的潜在风险。可以说useEvent通过限制handlerRef.current的更新时机来限制应用场景,最终达到稳定的目的。总结useEvent目前还处于RFC(RequestForComments)阶段。很多热心的开发者都建议了这个Hook的命名,比如:useStableCallback:和:useLatestClosure:从这些名字来看,显然是在扩展useEvent的应用场景。经过本文的分析,我们知道扩大应用场景意味着增加开发者在使用时出错的风险。