5.state 更新流程 (setState 里到底发生了什么)
人人都能读懂的 react 源码解析(大厂高薪必备)
5.state 更新流程(setState 里到底发生了什么)
视频课程 &调试 demos
视频课程的目的是为了快速掌握 react 源码运行的过程和 react 中的 scheduler、reconciler、renderer、fiber 等,并且详细 debug 源码和分析,过程更清晰。
视频课程:进入课程
demos:demo
课程结构:
上一节我们介绍了 react 两种模式的入口函数到 render 阶段的调用过程,也就是 mount 首次渲染的流程,这节我们介绍在更新状态之后到 render 阶段的流程。
在 react 中触发状态更新的几种方式:
ReactDOM.render
this.setState
this.forceUpdate
useState
useReducer
我们重点看下重点看下 this.setState 和 this.forceUpdate,hook 在第 11 章讲
1.this.setState 内调用 this.updater.enqueueSetState
2.this.forceUpdate 和 this.setState 一样,只是会让 tag 赋值 ForceUpdate
如果标记 ForceUpdate,render 阶段组件更新会根据 checkHasForceUpdateAfterProcessing,和 checkShouldComponentUpdate 来判断,如果 Update 的 tag 是 ForceUpdate,则 checkHasForceUpdateAfterProcessing 为 true,当组件是 PureComponent 时,checkShouldComponentUpdate 会浅比较 state 和 props,所以当使用 this.forceUpdate 一定会更新
3.enqueueForceUpdate 之后会经历创建 update,调度 update 等过程,接下来就来讲这些过程
状态更新整体流程

创建 Update
HostRoot 或者 ClassComponent 触发更新后,会在函数 createUpdate 中创建 update,并在后面的 render 阶段的 beginWork 中计算 Update。FunctionComponent 对应的 Update 在第 11 章讲,它和 HostRoot 或者 ClassComponent 的 Update 结构有些不一样
我们主要关注这些参数:
lane:优先级(第 12 章讲)
tag:更新的类型,例如 UpdateState、ReplaceState
payload:ClassComponent 的 payload 是 setState 第一个参数,HostRoot 的 payload 是 ReactDOM.render 的第一个参数
callback:setState 的第二个参数
next:连接下一个 Update 形成一个链表,例如同时触发多个 setState 时会形成多个 Update,然后用 next 连接
updateQueue:
对于 HostRoot 或者 ClassComponent 会在 mount 的时候使用 initializeUpdateQueue 创建 updateQueue,然后将 updateQueue 挂载到 fiber 节点上
baseState:初始 state,后面会基于这个 state,根据 Update 计算新的 state
firstBaseUpdate、lastBaseUpdate:Update 形成的链表的头和尾
shared.pending:新产生的 update 会以单向环状链表保存在 shared.pending 上,计算 state 的时候会剪开这个环状链表,并且链接在 lastBaseUpdate 后
effects:calback 不为 null 的 update
从 fiber 节点向上遍历到 rootFiber
在 markUpdateLaneFromFiberToRoot 函数中会从触发更新的节点开始向上遍历到 rootFiber,遍历的过程会处理节点的优先级(第 12 章讲)
调度
在 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

带优先级的状态更新
类似 git 提交,这里的 c3 意味着高优先级的任务,比如用户出发的事件,数据请求,同步执行的代码等。
通过 ReactDOM.render 创建的应用没有优先级的概念,类比 git 提交,相当于先 commit,然后提交 c3

在 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 重新计算
注意,fiber.updateQueue.shared 会同时存在于 workInprogress Fiber 和 current Fiber,目的是为了防止高优先级打断正在进行的计算而导致状态丢失,这段代码也是发生在 processUpdateQueue 中

版权声明: 本文为 InfoQ 作者【全栈潇晨】的原创文章。
原文链接:【http://xie.infoq.cn/article/4004aedb36c928ee1b83d2b08】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论