React官方团队出手,补齐原生Hook

新媒体求职招聘微信群 http://jdsshang.com/shenghuo/24914.html

作者:卡颂

简介:《React技术揭秘》作者

来源:SegmentFault思否社区

大家好,我卡颂。

我们知道,Hooks使用时存在所谓的闭包陷阱,考虑如下代码:

functionChat(){const[text,setText]=useState();constonClick=useCallback(()={sendMessage(text);},[]);turnSendButtononClick={onClick}/;}

我们期望点击后sendMessage能传递text的最新值。

然而实际上,由于回调函数被useCallback缓存,形成闭包,所以点击的效果始终是sendMessage()。

这就是闭包陷阱。

以上代码的一种解决方式是为useCallback增加依赖项:

constonClick=useCallback(()={sendMessage(text);},[text]);

但是这么做了后,每当依赖项(text)变化,useCallback会返回一个全新的onClick引用,这就失去了useCallback缓存函数引用的作用。

闭包陷阱的出现,加大了Hooks的上手门槛,也让开发者更容易写出有bug的代码。

现在,React官方团队要出手解决这个问题。

useEvent

解决方式是引入一个新的原生Hook——useEvent。

他用于定义一个函数,这个函数有个特性:

在组件多次nder时保持引用一致

函数内始终能获取到最新的props与state

上面的例子使用useEvent改造后:

functionChat(){const[text,setText]=useState();constonClick=useEvent(()={sendMessage(text);});turnSendButtononClick={onClick}/;}

在Chat组件多次nder时,onClick始终指向同一个引用。

并且onClick触发时始终能获取到text的最新值。

之所以叫useEvent,是因为React团队认为这个Hook的主要应用场景是:封装事件处理函数。

useEvent的实现

useEvent的实现并不困难,代码类似如下:

functionuseEvent(handler){consthandlerRef=useRef(null);//视图渲染完成后更新`handlerRef.curnt`指向useLayoutEffect(()={handlerRef.curnt=handler;});//用useCallback包裹,使得nder时返回的函数引用一致turnuseCallback((...args)={constfn=handlerRef.curnt;turnfn(...args);},[]);}

整体包括两部分:

返回一个没有依赖项的useCallback,使得每次nder时函数的引用一致

useCallback((...args)={constfn=handlerRef.curnt;turnfn(...args);},[]);

在合适的时机更新handlerRef.curnt,使得实际执行的函数始终是最新的引用

与开源Hooks的差异

很多开源Hooks库已经实现类似功能(比如ahooks中的useMemoizedFn)

useEvent与这些开源实现的差异主要体现在:

useEvent定位于处理事件回调函数这一单一场景,而useMemoizedFn定位于缓存各种函数。

那么问题来了,既然功能类似,那useEvent为什么要限制自己的使用场景呢?

答案是:为了更稳定。

useEvent能否获取到最新的state与props取决于handlerRef.curnt更新的时机。

在上面模拟实现中,useEvent更新handlerRef.curnt的逻辑放在useLayoutEffect回调中进行。

这就保证了handlerRef.curnt始终在视图完成渲染后再更新:

useLayoutEffect(()={handlerRef.curnt=handler;});

而事件回调触发的时机显然在视图完成渲染之后,所以能够稳定获取到最新的state与props。

注:源码内的实际更新时机会更早些,但不影响这里的结论

再来看看ahooks中的useMemoizedFn,fnRef.curnt的更新时机是useMemoizedFn执行时(即组件nder时):

functionuseMemoizedFnTextendsnoop(fn:T){constfnRef=useRefT(fn);//更新fnRef.curntfnRef.curnt=useMemo(()=fn,[fn]);//...省略代码}

当React18启用并发更新后,组件nder的次数、时机并不确定。

所以useMemoizedFn中fnRef.curnt的更新时机也是不确定的。

这就增加了在并发更新下使用时潜在的风险。

可以说,useEvent通过限制handlerRef.curnt更新时机,进而限制应用场景,最终达到稳定的目的。

总结

useEvent当前还处于RFC(RequestForComments)阶段。

很多热心的开发者对这个Hook的命名提出了建议,比如:

useStableCallback:

又比如:useLatestClosu:

从这些命名看,他们显然扩大了useEvent的应用场景。

经过本文的分析我们知道,扩大应用场景意味着增加开发者使用时出错的风险。

点击左下角阅读原文,到SegmentFault思否社区和文章作者展开更多互动和交流,扫描下方”


转载请注明:http://www.aierlanlan.com/rzgz/538.html

  • 上一篇文章:
  •   
  • 下一篇文章: