zoukankan      html  css  js  c++  java
  • 5.state更新流程(setState里到底发生了什么)

    人人都能读懂的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
      }
      
      

      状态更新整体流程

      _18

      创建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

      _19

    带优先级的状态更新

    ​ 类似git提交,这里的c3意味着高优先级的任务,比如用户出发的事件,数据请求,同步执行的代码等。

    • 通过ReactDOM.render创建的应用没有优先级的概念,类比git提交,相当于先commit,然后提交c3

      _20

    • 在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中

    _21

  • 相关阅读:
    Oracle DBA 数据库结构试题2
    Oracle DBA启动和关闭例程试题
    Oracle 命令大汇总备份与恢复
    数据库管理应注意的问题
    Using ICSharpCode.SharpZipLib for zip file
    SQL 2005 新功能
    ASP.net的RUL重写
    datalist 的 Datasource怎样绑定 泛型 List
    文件压缩/解压缩开源项目SharpZipLib在C#中的使用
    asp.net页面间传值的9种方式
  • 原文地址:https://www.cnblogs.com/xiaochen1024/p/14413074.html
Copyright © 2011-2022 走看看