react源码系列-更新流程

669 阅读2分钟

更新流程和优先级

在dispatchAction 中创建update对象

在函数组件中,多个hooks会组成单向链表,每一个hook中会有个生成好环状链表以后调用schedulUpdateOnFiber方法

schedulUpdateOnFiber:在这个fiber中调度这个update

从fiber找到root以后,使用 markUpdateLaneFromFiberToRoot 会从该fiber节点一直向上查找,

调用 markRootUpdated

更新流程大概这个流程。

优先级的种类:

    NoPriority = 0; // 没有优先级

    ImmediatePriority = 1; // 立即执行优先级

    UserBlockingPriority = 2; // 用户交互优先级

    NormalPriority = 3; // 普通优先级

    LowPriority = 4; // 低优先级

    IdlePriority = 5; // 可中断优先级

在整个更新过程中,如果一个触发了一个普通的setState,会触发一个优先级为NormalPriority的更新,接下来会一直向上查找直到root,在fiberRoot上会保存一个NormalPriority,已NormalPriority优先级 调度整个应用的根节点。

我们简单看一个代码来分析页面表现(concurrent mode):

 import { useState, useRef, useEffect } from 'react';
 export defult () => {
  const [count, updateCount] = useState(0);
  const buttonRef = useRef(null)
  const onclick = () => {
    updateCount(count => count + 2)
  }

  useEffect(() => {
    setTimeout(() => { updateCount(1) }, 1000)
    setTimeout(() => buttonRef.current?.click(), 1001)
  }, [])
  return (
    <div className="App">
      <button ref={buttonRef} onClick={onclick}>增加2</button>
      <div>{
        Array.from(new Array(15000)).map((item, index) => <span key={index}>{count}</span>)
      }</div>
    </div>
  );
 }

这是一个高优先级任务抢占低优先级任务的问题:

    在刚开始渲染页面的时候初始状态为0 页面渲染15000个0,这个没有问题。    

    在过1000毫秒以后,会有一个NormalPriority优先级的更新 updateCount(1),该更新找到root以后,产生更新调度,有15000个fiber节点要对比更新。然后这个是一个耗时的操作。

在1001毫秒的时候,又产生一个UserBlockingPriority优先级的更新buttonRef.current?.click(),此时上面的更新被打断跳过。

baseState: 0 结合 updateCount(count => count + 2) 产生 memoizedState: 2。页面会先渲染出2。

下次更新为跳过的update 和 后面的所有的update,baseState为跳过是的值为0。所有会有updateCount(1) 和 updateCount(count => count + 2),产生 memoizedState: 3,页面会渲染出3。

this.setSetate流程

当在组件中调用this.setState的时候,会进入

Component.prototype.setState = (state, callback) => {
    this.updater.enqueueState(this, state, callback)
}

而enqueueState方法:

会根据当前实例获取当前的fiber,

接着获取当前时间,

然后获取lane优先级相关的,

然后创建update,update.payload = state

enqueueSetState: function (inst, payload, callback) {
    var fiber = get(inst);
    var eventTime = requestEventTime();
    var lane = requestUpdateLane(fiber);
    var update = createUpdate(eventTime, lane);
    update.payload = payload;

    if (callback !== undefined && callback !== null) {
       update.callback = callback;
    }

    enqueueUpdate(fiber, update);
    var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
    
    markStateUpdateScheduled(fiber, lane);
  },

所以就是回到了文章开头的更新流程了。

花未全开月未圆

半山微醉尽余欢

何须多虑盈亏事

终归小满胜万全