React架构的演进——Hooks的实现
时间:2023-03-13 06:49:12
科技观察
ReactHooks可以说是彻底颠覆了以往ClassComponents的写法,进一步增强了状态复用的能力,让FunctionComponents也有了内部状态。就个人而言,我更喜欢HowtowriteHooks。当然,如果你是一个使用ClassComponent的老手,那么上手的时候你会觉得很苦恼。毕竟之前积累的很多HOC和RenderProps组件基本都用不上了。而且,之前的FunctionComponent是无状态组件,没有副作用,现在可以通过Hooks引入state,看起来确实很迷惑。FunctionComponent的另一个好处是你可以彻底告别这个。在ClassComponent中,这真是一件令人讨厌的事情??。Hooks是如何关联组件的,在之前的文章中已经多次提到。Fiber架构下的updateQueue和effectList都是链表数据结构,然后挂载到Fiber节点上。一个函数组件中的所有Hooks也是以链表的形式存储的,最后挂载在fiber.memoizedState上。functionApp(){const[num,updateNum]=useState(0)returnupdateNum(num=>num+1)}>{num}
}exportdefaultApp先简单看一下,调用useState时,构造链表的过程:varworkInProgressHook=nullvarHooksDispatcherOnMount={useState:function(initialState){returnmountState(initialState)}}functionfunctionmountState(initialState){//新建Hook节点varhook=mountWorkInProgressHook()//缓存初始值挂钩。memoizedState=initialState//构造一个更新队列,类似于fiber.updateQueuevarqueue=hook.queue={pending:null,dispatch:null,lastRenderedState:initialState}//用于调度更新vardispatch=queue.dispatch=dispatchAction.bind(null,workInProgress,queue)//[num,updateNum]=useState(0)return[hook.memoizedState,dispatch]}functionmountWorkInProgressHook(){varhook={memoizedState:null,baseState:null,baseQueue:null,queue:null,next:null}if(workInProgressHook===null){//构造链表的头节点workInProgress.memoizedState=workInProgressHook=hook}else{//如果链表已经存在,挂载到nextworkInProgressHook=workInProgressHook.next=hook}返回workInProgressHook}如果此时有两个Hook,第二个Hook会挂载到第一个Hook的下一个属性上functionApp(){const[num,updateNum]=useState(0)const[str,updateStr]=useState('value:')return
updateNum(num=>num+1)}>{str}{num} }exportdefaultAppHookHook的更新队列Hook通过.next相互连接,而每一个Hook对象下,还有一个queue字段,和Fiber节点上的updateQueue是一样的。它是一个更新队列。上篇文章《React 架构的演变-更新机制》中提到,在ReactFiber架构中,更新队列是通过链表结构存储的。classAppextendsReact.Component{state={val:0}click(){for(leti=0;i<3;i++){this.setState({val:this.state.val+1})}}render(){return
{this.click()}}>val:{this.state.val}?/div>}}点击div后生成的3组setState挂载到fiber中链表的形式在.updateQueue上,MessageChannel收到通知后,真正执行更新操作时,取出更新队列,将计算结果更新到fiber.memoizedState。setState和hook.queue的逻辑和fiber.updateQueue完全一样。functionApp(){const[num,updateNum]=useState(0)return{//连续更新3次updateNum(num=>num+1)updateNum(num=>num+1)updateNum(num=>num+1)}}>{num} }exportdefaultApp;vardispatch=queue.dispatch=dispatchAction.bind(null,workInProgress,queue)//[num,updateNum]=useState(0)return[hook.memoizedState,dispatch]调用useState时,返回数组的第二个参数为dispatch,dispatch是在dispatchAction绑定后得到的。functiondispatchAction(fiber,queue,action){varupdate={next:null,action:action,//省略调度相关参数...};varpending=queue.pendingif(pending===null){update.next=update}else{update.next=pending.nextpending.next=update}queue.pending=update//执行更新scheduleUpdateOnFiber()}可以看到这里构造链表的方式和fiber.updateQueue完全一样。之前我们通过updateNum连续更新了3次num,最终的更新队列如下:更新队列功能组件的更新上一篇分享过,Fiber架构下的更新流程分为两个types:beginWork和completeWork步骤,在beginWork中,会根据组件类型进行render操作,构造子组件。functionbeginWork(current,workInProgress){switch(workInProgress.tag){//其他类型的组件代码省略...caseFunctionComponent:{//这里的类型是函数组件的函数//比如前面的App组件,类型为functionApp(){}varComponent=workInProgress.typevarresolvedProps=workInProgress.pendingProps//组件更新returnupdateFunctionComponent(current,workInProgress,Component,resolvedProps)}}}functionupdateFunctionComponent(current,workInProgress,Component,nextProps){//构造子组件varnextithChildren=renderW,workInProgress,Component,nextProps)reconcileChildren(current,workInProgress,nextChildren)returnworkInProgress.child}从名字就可以看出,renderWithHooks方法就是用Hooks构造子组件。functionrenderWithHooks(current,workInProgress,Component,props){if(current!==null&¤t.memoizedState!==null){ReactCurrentDispatcher.current=HooksDispatcherOnUpdate}else{ReactCurrentDispatcher.current=HooksDispatcherOnMount}varchildren=renpatcherOnMount}从上面返回从代码,可以看出,当函数组件第一次更新或者渲染时,本质就是把函数取出来重新执行。不同的是ReactCurrentDispatcher被赋予了不同的值,ReactCurrentDispatcher的值最终会影响useState调用不同的方法。根据前文提到的双缓冲机制,当current存在时表示更新操作,不存在时表示第一次渲染。functionuseState(initialState){//首次渲染时指向HooksDispatcherOnMount//指向HooksDispatcherOnUpdatevardispatcher=ReactCurrentDispatcher.currentreturndispatcher.useState(initialState)}HooksDispatcherOnMount.useState的代码前面已经介绍过,这里不再赘述。//HooksDispatcherOnMount的代码前面已经介绍过了varHooksDispatcherOnMount={useState:function(initialState){returnmountState(initialState)}}下面重点介绍一下HooksDispatcherOnMount.useState的逻辑。varHooksDispatcherOnUpdateInDEV={useState:function(initialState){returnupdateState()}}functionupdateState(){//取出当前hookworkInProgressHook=nextWorkInProgressHooknextWorkInProgressHook=workInProgressHook.nextvarhook=nextWorkInProgressHookvarqueue=hook.queuevarpendingQueue=queue.pending//处理更新varfirst=pendingQueue.nextvarstate=hook.memoizedStatevarupdate=firstdo{varaction=update.actionstate=typeofaction==='function'?action(state):actionupdate=update.next;}while(更新!==null&&update!==first)hook.memoizedState=statevardispatch=queue.dispatchreturn[hook.memoizedState,dispatch]}看之前的setState代码,其实这里的逻辑是一样的。取出更新对象的动作,如果是函数就执行,如果不是函数就直接替换状态。总结React系列的文章终于写完了。这篇文章应该是最简单的一篇了。如果想忽略React的源码,单看Hooks的实现,可以看这篇文章:《React Hooks 原理》。为了能够循环更新,Fiber架构将所有涉及数据的局部结构都改为链表。这样做的好处是可以随时中断,为异步模式让路。Fiber树就像一棵圣诞树,上面挂满了各种灯(alternate,EffectList,updateQueue,Hooks)。本文转载自微信?《更神奇的前端》,可通过以下二维码关注。转载本文请联系更牛逼前端?。