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
-
以下源码浅析的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
中会将当前的workInProgress
Fiber节点存在全局变量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
自带了一个basicStateReducer
mountReducer
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
,创建一个新的更新u1
update.next = pending.next
即为u1.next = u0.next
,上一次执行时u0.next -> u0
,结果为u1.next = u0
pending.next = update
这时候pending
是u0
,即为u0.next = u1
queue.pending = update
即为queue.pending = u1
,这时候最终结果为queue.pending(u1) -> u0 -> u1
第三次执行的时候,创建一个新的更新
u2
update.next = pending.next
即为u2.next = u1.next
,上一次执行时queue.pending(u1) -> u0 -> u1
,结果为u2.next = u0
pending.next = update
这时候pending
是u1
,即为u1.next = u2
queue.pending = update
即为queue.pending = u2
这里仔细思考,实际上只改变了头和尾,中间的连接(
u0 - > u1
)没有改变,所以最后的结果为queue.pending(u2) -> u0 -> u1 -> u2
最终的环形链表的
pending
始终指向最新的update
,而最新的update.next
指向第一个更新u0
updateState
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