课程地址 https://xiaochen1024.com/courseware/60b1b2f6cf10a4003b634718/60b1b36ecf10a4003b634724
在react中触发状态更新的几种方式:
ReactDOM.render
this.setState
this.forceUpdate
useState
useReducer
我们重点看下重点看下 this.setState
和 this.forceUpdate
this.setState
内调用 this.updater.enqueueSetState
,主要是将 update
加入 updateQueue
中
enqueueUpdate
用来将 update
加入 updateQueue
队列
this.forceUpdate
和 this.setState
一样,只是会让 tag
赋值 ForceUpdate
如果标记 ForceUpdate
,render阶段
组件更新会根据 checkHasForceUpdateAfterProcessing
,和 checkShouldComponentUpdate
来判断,如果 Update
的 tag
是ForceUpdate
,则 checkHasForceUpdateAfterProcessing
为true,当组件是 PureComponent
时,checkShouldComponentUpdate
会浅比较 state
和 props
,所以当使用 this.forceUpdate
一定会更新
HostRoot
或者 ClassComponent
触发更新后,会在函数 createUpdate
中创建 update
,并在后面的 render
阶段的 beginWork
中计算 Update
。FunctionComponent
对应的 Update
在第11章讲,它和 HostRoot
或者 ClassComponent
的 Update
结构有些不一样
我们主要关注这些参数:
lane
:优先级tag
:更新的类型,例如UpdateState
、ReplaceState
payload
:ClassComponent
的payload
是setState
第一个参数,HostRoot
的payload
是ReactDOM.render
的第一个参数callback
:setState
的第二个参数next
:连接下一个Update
形成一个链表,例如同时触发多个setState
时会形成多个Update
,然后用next
连接
对于 HostRoot
或者 ClassComponent
会在 mount
的时候使用 initializeUpdateQueue
创建 updateQueue
,然后将 updateQueue
挂载到 fiber
节点上
baseState
:初始state,后面会基于这个state,根据Update
计算新的statefirstBaseUpdate
、lastBaseUpdate
:Update
形成的链表的头和尾shared.pending
:新产生的update
会以单向环状链表
保存在shared.pending
上,计算state的时候会剪开这个环状链表,并且链接在lastBaseUpdate
后effects
:calback
不为null的update
从触发更新的 fiber
节点向上遍历到 rootFiber
在 markUpdateLaneFromFiberToRoot
函数中会从触发更新的节点开始向上遍历到 rootFiber
,遍历的过程会处理节点的优先级
调度
在 ensureRootIsScheduled
中,scheduleCallback
会以一个优先级调度 render阶段
的开始函数 performSyncWorkOnRoot
或者 performConcurrentWorkOnRoot
状态更新
classComponent
状态计算发生在 processUpdateQueue
函数中,涉及很多链表操作,看图更加直白
- 初始时
fiber.updateQueue单链表
上有firstBaseUpdate(update1)
和lastBaseUpdate(update2)
,以next
连接 fiber.updateQueue.shared环状链表
上有update3
和update4
,以next
连接互相连接- 计算state时,先将
fiber.updateQueue.shared环状链表‘剪开’,形成单链表
,连接在fiber.updateQueue
后面形成baseUpdate
- 然后遍历按这条链表,根据
baseState
计算出memoizedState
带优先级的状态更新
- 通过
ReactDOM.render
创建的应用没有优先级的概念 - 在
concurrent
模式下,类似git rebase,先暂存之前的代码,在master上开发,然后rebase到之前的分支上
优先级是由 Scheduler
来调度的,这里我们只关心状态计算时的优先级排序,也就是在函数 processUpdateQueue
中发生的计算,例如初始时有c1-c4四个update,其中c1和c3为高优先级
在第一次render的时候,低优先级的update
会跳过,所以只有c1和c3加入 状态的计算
在第二次render的时候,会以第一次中跳过的update(c2)之前的update(c1)作为baseState,跳过的update和之后的update(c2,c3,c4)作为 baseUpdate
重新计算
在在concurrent模式下,componentWillMount可能会执行多次,变现和之前的版本不一致
注意,fiber.updateQueue.shared
会同时存在于 workInprogress Fiber
和 current Fiber
,目的是为了防止高优先级打断正在进行的计算而导致状态丢失,这段代码也是发生在 processUpdateQueue
中