写点什么

5.state 更新流程 (setState 里到底发生了什么)

用户头像
全栈潇晨
关注
发布于: 2021 年 02 月 18 日

人人都能读懂的 react 源码解析(大厂高薪必备)

5.state 更新流程(setState 里到底发生了什么)

视频课程 &调试 demos

​ 视频课程的目的是为了快速掌握 react 源码运行的过程和 react 中的 scheduler、reconciler、renderer、fiber 等,并且详细 debug 源码和分析,过程更清晰。

​ 视频课程:进入课程

​ demos:demo

课程结构:

  1. 开篇(听说你还在艰难的啃react源码)

  2. react心智模型(来来来,让大脑有react思维吧)

  3. Fiber(我是在内存中的dom)

  4. 从legacy或concurrent开始(从入口开始,然后让我们奔向未来)

  5. state更新流程(setState里到底发生了什么)

  6. render阶段(厉害了,我有创建Fiber的技能)

  7. commit阶段(听说renderer帮我们打好标记了,映射真实节点吧)

  8. diff算法(妈妈再也不担心我的diff面试了)

  9. hooks源码(想知道Function Component是怎样保存状态的嘛)

  10. scheduler&lane模型(来看看任务是暂停、继续和插队的)

  11. concurrent mode(并发模式是什么样的)

  12. 手写迷你react(短小精悍就是我)

​ 上一节我们介绍了 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

Component.prototype.setState = function (partialState, callback) {  if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) {    {      throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." );    }  }  this.updater.enqueueSetState(this, partialState, callback, 'setState');};
复制代码

2.this.forceUpdate 和 this.setState 一样,只是会让 tag 赋值 ForceUpdate

enqueueForceUpdate(inst, callback) {    const fiber = getInstance(inst);    const eventTime = requestEventTime();    const suspenseConfig = requestCurrentSuspenseConfig();    const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig); //tag赋值ForceUpdate update.tag = ForceUpdate; if (callback !== undefined && callback !== null) { update.callback = callback; } enqueueUpdate(fiber, update); scheduleUpdateOnFiber(fiber, lane, eventTime);
},};
复制代码

如果标记 ForceUpdate,render 阶段组件更新会根据 checkHasForceUpdateAfterProcessing,和 checkShouldComponentUpdate 来判断,如果 Update 的 tag 是 ForceUpdate,则 checkHasForceUpdateAfterProcessing 为 true,当组件是 PureComponent 时,checkShouldComponentUpdate 会浅比较 state 和 props,所以当使用 this.forceUpdate 一定会更新

const shouldUpdate =  checkHasForceUpdateAfterProcessing() ||  checkShouldComponentUpdate(    workInProgress,    ctor,    oldProps,    newProps,    oldState,    newState,    nextContext,  );
复制代码

3.enqueueForceUpdate 之后会经历创建 update,调度 update 等过程,接下来就来讲这些过程

enqueueSetState(inst, payload, callback) {  const fiber = getInstance(inst);//fiber实例
const eventTime = requestEventTime(); const suspenseConfig = requestCurrentSuspenseConfig(); const lane = requestUpdateLane(fiber, suspenseConfig);//优先级
const update = createUpdate(eventTime, lane, suspenseConfig);//创建update
update.payload = payload;
if (callback !== undefined && callback !== null) { //赋值回调 update.callback = callback; }
enqueueUpdate(fiber, update);//update加入updateQueue scheduleUpdateOnFiber(fiber, lane, eventTime);//调度update}

复制代码


状态更新整体流程


  • 创建 Update

HostRoot 或者 ClassComponent 触发更新后,会在函数 createUpdate 中创建 update,并在后面的 render 阶段的 beginWork 中计算 Update。FunctionComponent 对应的 Update 在第 11 章讲,它和 HostRoot 或者 ClassComponent 的 Update 结构有些不一样

export function createUpdate(eventTime: number, lane: Lane): Update<*> {//创建update  const update: Update<*> = {    eventTime,    lane,
tag: UpdateState, payload: null, callback: null,
next: null, }; return 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 节点上

export function initializeUpdateQueue<State>(fiber: Fiber): void {  const queue: UpdateQueue<State> = {    baseState: fiber.memoizedState,    firstBaseUpdate: null,    lastBaseUpdate: null,  shared: {      pending: null,    },    effects: null,  };fiber.updateQueue = queue;}
复制代码

baseState:初始 state,后面会基于这个 state,根据 Update 计算新的 state

  • firstBaseUpdate、lastBaseUpdate:Update 形成的链表的头和尾

  • shared.pending:新产生的 update 会以单向环状链表保存在 shared.pending 上,计算 state 的时候会剪开这个环状链表,并且链接在 lastBaseUpdate 后

  • effects:calback 不为 null 的 update


从 fiber 节点向上遍历到 rootFiber

在 markUpdateLaneFromFiberToRoot 函数中会从触发更新的节点开始向上遍历到 rootFiber,遍历的过程会处理节点的优先级(第 12 章讲)


function markUpdateLaneFromFiberToRoot(    sourceFiber: Fiber,    lane: Lane,  ): FiberRoot | null {    sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);    let alternate = sourceFiber.alternate;    if (alternate !== null) {      alternate.lanes = mergeLanes(alternate.lanes, lane);    }    let node = sourceFiber;    let parent = sourceFiber.return;    while (parent !== null) {//从触发更新的节点开始向上遍历到rootFiber      parent.childLanes = mergeLanes(parent.childLanes, lane);//合并childLanes优先级      alternate = parent.alternate;      if (alternate !== null) {        alternate.childLanes = mergeLanes(alternate.childLanes, lane);      } else {      }      node = parent;      parent = parent.return;    }    if (node.tag === HostRoot) {      const root: FiberRoot = node.stateNode;      return root;    } else {      return null;    }  }
复制代码


调度

​ 在 ensureRootIsScheduled 中,scheduleCallback 会以一个优先级调度 render 阶段的开始函数 performSyncWorkOnRoot 或者 performConcurrentWorkOnRoot

if (newCallbackPriority === SyncLanePriority) {  // 任务已经过期,需要同步执行render阶段  newCallbackNode = scheduleSyncCallback(    performSyncWorkOnRoot.bind(null, root)  );} else {  // 根据任务优先级异步执行render阶段  var schedulerPriorityLevel = lanePriorityToSchedulerPriority(    newCallbackPriority  );  newCallbackNode = scheduleCallback(    schedulerPriorityLevel,    performConcurrentWorkOnRoot.bind(null, root)  );}
复制代码

状态更新

​ 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 中



发布于: 2021 年 02 月 18 日阅读数: 14
用户头像

全栈潇晨

关注

还未添加个人签名 2021.02.17 加入

还未添加个人简介

评论

发布
暂无评论
5.state更新流程(setState里到底发生了什么)