写点什么

react 源码解析 12. 状态更新流程

作者:zz1998
  • 2022 年 4 月 14 日
  • 本文字数:6611 字

    阅读完需:约 22 分钟

react 源码解析 12.状态更新流程

视频讲解(高效学习):进入学习

往期文章:

1.开篇介绍和面试题


2.react的设计理念


3.react源码架构


4.源码目录结构和调试


5.jsx&核心api


6.legacy和concurrent模式入口函数


7.Fiber架构


8.render阶段


9.diff算法


10.commit阶段


11.生命周期


12.状态更新流程


13.hooks源码


14.手写hooks


15.scheduler&Lane


16.concurrent模式


17.context


18事件系统


19.手写迷你版react


20.总结&第一章的面试题解答


21.demo

setState&forceUpdate

在 react 中触发状态更新的几种方式:


  • ReactDOM.render

  • this.setState

  • this.forceUpdate

  • useState

  • useReducer


我们重点看下重点看下 this.setState 和 this.forceUpdate,hook 在第 13 章讲


  1. this.setState 内调用 this.updater.enqueueSetState,主要是将 update 加入 updateQueue 中


   //ReactBaseClasses.js   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');   };
复制代码


   //ReactFiberClassComponent.old.js   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   }
复制代码


enqueueUpdate 用来将 update 加入 updateQueue 队列


   //ReactUpdateQueue.old.js   export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {     const updateQueue = fiber.updateQueue;     if (updateQueue === null) {       return;     }        const sharedQueue: SharedQueue<State> = (updateQueue: any).shared;     const pending = sharedQueue.pending;     if (pending === null) {       update.next = update;//与自己形成环状链表     } else {       update.next = pending.next;//加入链表的结尾       pending.next = update;     }     sharedQueue.pending = update;   }
复制代码



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


   //ReactBaseClasses.js   Component.prototype.forceUpdate = function(callback) {     this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');   };
复制代码


   //ReactFiberClassComponent.old.js   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 一定会更新


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


状态更新整体流程


Update&updateQueue

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


//ReactUpdateQueue.old.jsexport 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 连接


对于 HostRoot 或者 ClassComponent 会在 mount 的时候使用 initializeUpdateQueue 创建 updateQueue,然后将 updateQueue 挂载到 fiber 节点上


//ReactUpdateQueue.old.jsexport 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,遍历的过程会处理节点的优先级(第 15 章讲)


//ReactFiberWorkLoop.old.jsfunction 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;    }  }
复制代码


例如 B 节点触发更新,B 节点被被标记为 normal 的 update,也就是图中的 u1,然后向上遍历到根节点,在根节点上打上一个 normal 的 update,如果此时 B 节点又触发了一个 userBlocking 的 Update,同样会向上遍历到根节点,在根节点上打上一个 userBlocking 的 update。


如果当前根节点更新的优先级是 normal,u1、u2 都参与状态的计算,如果当前根节点更新的优先级是 userBlocking,则只有 u2 参与计算


调度

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


//ReactFiberWorkLoop.old.jsif (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 重新计算

  • 在在 concurrent 模式下,componentWillMount 可能会执行多次,变现和之前的版本不一致

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



看 demo_8 的优先级


现在来看下计算状态的函数


//ReactUpdateQueue.old.jsexport function processUpdateQueue<State>(  workInProgress: Fiber,  props: any,  instance: any,  renderLanes: Lanes,): void {  const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);  hasForceUpdate = false;
let firstBaseUpdate = queue.firstBaseUpdate;//updateQueue的第一个Update let lastBaseUpdate = queue.lastBaseUpdate;//updateQueue的最后一个Update let pendingQueue = queue.shared.pending;//未计算的pendingQueue
if (pendingQueue !== null) { queue.shared.pending = null; const lastPendingUpdate = pendingQueue;//未计算的ppendingQueue的最后一个update const firstPendingUpdate = lastPendingUpdate.next;//未计算的pendingQueue的第一个update lastPendingUpdate.next = null;//剪开环状链表 if (lastBaseUpdate === null) {//将pendingQueue加入到updateQueue firstBaseUpdate = firstPendingUpdate; } else { lastBaseUpdate.next = firstPendingUpdate; } lastBaseUpdate = lastPendingUpdate;
const current = workInProgress.alternate;//current上做同样的操作 if (current !== null) { const currentQueue: UpdateQueue<State> = (current.updateQueue: any); const currentLastBaseUpdate = currentQueue.lastBaseUpdate; if (currentLastBaseUpdate !== lastBaseUpdate) { if (currentLastBaseUpdate === null) { currentQueue.firstBaseUpdate = firstPendingUpdate; } else { currentLastBaseUpdate.next = firstPendingUpdate; } currentQueue.lastBaseUpdate = lastPendingUpdate; } } }
if (firstBaseUpdate !== null) { let newState = queue.baseState;
let newLanes = NoLanes;
let newBaseState = null; let newFirstBaseUpdate = null; let newLastBaseUpdate = null;
let update = firstBaseUpdate; do { const updateLane = update.lane; const updateEventTime = update.eventTime; if (!isSubsetOfLanes(renderLanes, updateLane)) {//判断优先级是够足够 const clone: Update<State> = {//优先级不够 跳过当前update eventTime: updateEventTime, lane: updateLane,
tag: update.tag, payload: update.payload, callback: update.callback,
next: null, }; if (newLastBaseUpdate === null) {//保存跳过的update newFirstBaseUpdate = newLastBaseUpdate = clone; newBaseState = newState; } else { newLastBaseUpdate = newLastBaseUpdate.next = clone; } newLanes = mergeLanes(newLanes, updateLane); } else { //直到newLastBaseUpdate为null才不会计算,防止updateQueue没计算完 if (newLastBaseUpdate !== null) { const clone: Update<State> = { eventTime: updateEventTime, lane: NoLane,
tag: update.tag, payload: update.payload, callback: update.callback,
next: null, }; newLastBaseUpdate = newLastBaseUpdate.next = clone; }
newState = getStateFromUpdate(//根据updateQueue计算state workInProgress, queue, update, newState, props, instance, ); const callback = update.callback; if (callback !== null) { workInProgress.flags |= Callback;//Callback flag const effects = queue.effects; if (effects === null) { queue.effects = [update]; } else { effects.push(update); } } } update = update.next;//下一个update if (update === null) {//重置updateQueue pendingQueue = queue.shared.pending; if (pendingQueue === null) { break; } else { const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>); lastPendingUpdate.next = null; update = firstPendingUpdate; queue.lastBaseUpdate = lastPendingUpdate; queue.shared.pending = null; } } } while (true);
if (newLastBaseUpdate === null) { newBaseState = newState; }
queue.baseState = ((newBaseState: any): State);//新的state queue.firstBaseUpdate = newFirstBaseUpdate;//新的第一个update queue.lastBaseUpdate = newLastBaseUpdate;//新的最后一个update
markSkippedUpdateLanes(newLanes); workInProgress.lanes = newLanes; workInProgress.memoizedState = newState; }
//...}
复制代码


用户头像

zz1998

关注

还未添加个人签名 2021.06.26 加入

还未添加个人简介

评论

发布
暂无评论
react源码解析12.状态更新流程_React_zz1998_InfoQ写作平台