useState和useReducer源码浅析 #108
zhangyu1818
announced in
zh-cn
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
以下源码浅析的React版本为17.0.1,使用
ReactDOM.render创建的同步应用,不含优先级相关。流程简述
函数组件会调用
renderWithHooks函数,这个函数主要会标记当前渲染的currentlyRenderingFiber节点,并判断该使用哪一个HooksDispatcher(React里Mount和Update所使用的Hooks不是同一个),接着执行此函数组件,分别处理hook函数,并得到children,将children返回到上层函数后,执行reconcileChildren生成child子节点。renderWithHooks
renderWithHooks函数为函数组件的入口函数,无论是Mount时的mountIndeterminateComponent还是Update时的updateFunctionComponent都会进入这个函数来获取函数组件的children。renderWithHooks中会将当前的workInProgressFiber节点存在全局变量currentlyRenderingFiber中,这样方便后面获取Fiber信息,接着在调用函数获取children之前,先判断使用哪一个ReactCurrentDispatcher,最后返回children给上层函数处理。需要注意的是Mount时和Update时用的不是同一个hook。
再来看下
useState所以后续会分为Mount和Update来分析
Hooks的数据结构
单个
hook的数据结构如下那函数组件如何找到对应的
hooks信息呢?函数组件的hooks的信息储存在对应Fiber节点中的memoizedState字段中,代表函数组件里的第一个hook,hooks数据结构为单向链表,每一个节点可以通过next属性找到下一个hook。每一个
hook的更新队列都会存在queue字段里,通过执行队列里的操作就可以得到最新的state值,结构如下hook的更新队列是一个单向环形链表,pending字段保存的是最新的update,通过update.next可以获取第一个加入更新队列的update。useState和useReducer
useState实际是一个自带了reducer的useReducer语法糖,所以需要放在一起分析。mountState
mountState自带了一个basicStateReducermountReducer
mountState和mountReducer的区别在于函数参数和初始值赋值的不同,其他都是一样的。进入函数首先会通过
mountWorkInProgressHook来创建hook对象,接着初始化queue,通过bind将Fiber节点和queue传入dispatchAction函数实现部分参数,最后将值返回。mountWorkInProgressHook
mountWorkInProgressHook函数功能比较简单,创建一个hook对象,将多个hook对象连接为单向链表。dispatchAction
dispatchAction方法为用来创建一个新的update,来发起更新dispatchAction里会把更新连接进环形链表,如果不是render阶段的更新则会通过优先级判断Fiber节点上是否存在更新,如果不存在就会在dispatchAction里计算出新的state值,接着判断新旧值是否相同,相同就不需要发起更新调度了。当同步调用多次
dispatchAction就会产生多个update,会将他们组成环形链表。这里的环形链表连接比较难理解
第一次执行的时候,
pending === null,会创建一个自己连接自己的环形链表,这里表示为u0 -> u0。第二次执行的时候,
pending !== null,创建一个新的更新u1update.next = pending.next即为u1.next = u0.next,上一次执行时u0.next -> u0,结果为u1.next = u0pending.next = update这时候pending是u0,即为u0.next = u1queue.pending = update即为queue.pending = u1,这时候最终结果为queue.pending(u1) -> u0 -> u1第三次执行的时候,创建一个新的更新
u2update.next = pending.next即为u2.next = u1.next,上一次执行时queue.pending(u1) -> u0 -> u1,结果为u2.next = u0pending.next = update这时候pending是u1,即为u1.next = u2queue.pending = update即为queue.pending = u2这里仔细思考,实际上只改变了头和尾,中间的连接(
u0 - > u1)没有改变,所以最后的结果为queue.pending(u2) -> u0 -> u1 -> u2最终的环形链表的
pending始终指向最新的update,而最新的update.next指向第一个更新u0updateState
updateState调用的就完全是updateReducer了,只是传入了自带的reducer,所以updateState和updateReducer可谓是完全一致updateReducer
mountReducer返回的值是initialState,updateReducer返回的值则是通过调用依次queue中的update计算后的state值。updateReducer会依次将queue中的update放入reducer中计算,最后将新的state值赋值给hook.memoizedState并返回。updateWorkInProgressHook
updateWorkInProgressHook和mountWorkInProgressHook功能相似,会返回一个浅拷贝的hook对象,更改currentHook和workInProgressHook的指向,同时连接一个新的workInProgressHook链表。总结
函数组件通过
renderWithHooks可以确定当前的WorkInProgressFiber节点,通过是否存在currentFiber节点来判断当前为Mount还是Update,分别获取不同的ReactCurrentDispatcher,执行函数组件自己来获取children。执行过程中,同时会执行到对应的
hook函数,函数组件的hooks为单向链表存在Fiber节点的memoizedState字段上,通过hook.next可以顺序依次获取hook对象,每一个hook对象中存在memoizedState字段,对于useState和useReducer来说储存的即为state值本身,hook对象上存在queue代表当前hook的更新队列,为环形单向链表,queue.pending指向为最新的update,queue.pending.next执行为第一个update。通过执行
mountState和mountReducer来获取state初始值,通过执行updateState和updateReducer来计算queue中的update以获取最新的state值。调用
dispatchAction发起更新调度,同时在dispatchAction里会组装更新的queue环形单向链表,最后在render阶段会执行updateState和updateReducer来获取最新的state值。如有错误,还望交流指正。
Beta Was this translation helpful? Give feedback.
All reactions