react-dom setState

532 阅读2分钟

分析画的思维脑图,点击下载

目前对setState的基本认识:

1.setState异步更新数据,想要获取数据从回调函数中获取,

2.多次setState函数调用会合并执行

基于以上的认知,有这样几个问题:

1.setState 真的是异步吗?如果不是异步,同步与异步在什么场景下区分?

2.setState 如何对多次调用进行合并 源码注解和思维脑图

setState执行流程:

1.react

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.react-dom

/*
  状态更新
  var UpdateState = 0;// 更新状态
  var ReplaceState = 1;// 替换状态
  var ForceUpdate = 2; // 强制更新
  var CaptureUpdate = 3;// 捕获更新
*/

var classComponentUpdater = {
  isMounted: isMounted,
  enqueueSetState: function (inst, payload, callback) {
   // 获取fiber对象
    var fiber = get(inst);
    var currentTime = requestCurrentTimeForUpdate();
    var suspenseConfig = requestCurrentSuspenseConfig();
    var expirationTime = computeExpirationForFiber(currentTime, fiber, suspenseConfig);
    var update = createUpdate(expirationTime, suspenseConfig);
    update.payload = payload;
    //判断setState是有存在回调函数,如果存在的话赋值给更新队列的回调函数
    if (callback !== undefined && callback !== null) {
      {
        warnOnInvalidCallback$1(callback, 'setState');
      }
      update.callback = callback;
    }
    enqueueUpdate(fiber, update);
    scheduleWork(fiber, expirationTime);
  },
};

react 有几种更新方式,这里只有setState的相关代码

enqueueSetState执行会生成 fiber,update和expirationTime,

以下为例

  componentDidMount() {
       this.setState({
           testNum:3
       })
     this.setState({
         testNum:5
     })
   }

生命周期中两次调用setState,react 会将更新队列合并为链表(tree结构),所以最终的更新内容是{testNum: 5}

updateQueue:
 baseState:
   testNum: 0
   testNum1: 0
__proto__: Object
firstCapturedEffect: null
firstCapturedUpdate: null
firstEffect: null
firstUpdate:
   callback: null                 //回调函数
   expirationTime: 1073741823       //过期时间
   next:                         //下一个更新
     callback: null
     expirationTime: 1073741823
     next: null                         //如果还有setState还会往下链
     nextEffect: null
     payload: {testNum: 5}         //更新内容
     priority: 99                  // 优先级
     suspenseConfig: null
     tag: 0                        //0更新 1替换 2强制更新 3捕获性的更新
  nextEffect: null
  payload: {testNum: 3}
  priority: 99
  suspenseConfig: null
  tag: 0
lastCapturedEffect: null      //队列中第一个捕获类型的update
lastCapturedUpdate: null       //队列中最后一个捕获类型的update
lastEffect: null             //第一个side effect
lastUpdate:                  //最后一个side effect
   callback: null                 //回调函数
   expirationTime: 1073741823    //过期时间
   next: null
   nextEffect: null
   payload: {testNum: 5}      //更新内容
   priority: 99                // 优先级
   suspenseConfig: null
   tag: 0                          //0更新 1替换 2强制更新 3捕获性的更新




scheduleWork函数会执行异步走 ensureRootIsScheduled函数执行渲染流程

function scheduleUpdateOnFiber(fiber, expirationTime) {
//判断是否是无限循环update
// 函数 3-1-7-1
  checkForNestedUpdates();
  //测试环境用
  // 函数 3-1-7-2
  warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
   //找到rootFiber并遍历更新子节点的expirationTime
    // 函数 3-1-7-3
  var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
  if (root === null) {
  // 函数 3-1-7-4
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return;
  }
  //判断是否有高优先级任务打断当前正在执行的任务
  // 函数 3-1-7-5
  checkForInterruption(fiber, expirationTime);
  //报告调度更新,测试环境用的
  recordScheduleUpdate(); 
  var priorityLevel = getCurrentPriorityLevel();
   //如果expirationTime等于最大整型值的话
  //如果是同步任务的过期时间的话
  if (expirationTime === Sync) {
    if ( //检查一下我们是否在非batchedupdates里
    (executionContext & LegacyUnbatchedContext) !== NoContext && // 检查我们是否已经渲染
    (executionContext & (RenderContext | CommitContext)) === NoContext) {
      // 在根上注册挂起的交互,以避免丢失跟踪的交互数据。
      // 函数 3-1-7-6
      schedulePendingInteractions(root, expirationTime); 
      //这是一个遗留的边缘案例。反应物的初始量
      // batchedupdate的根目录应该是同步的,但是布局要更新
     //应该推迟到这批货的末尾。
      // 函数 3-1-7-7
      performSyncWorkOnRoot(root);
    } else {
    // 函数 3-1-7-8
      ensureRootIsScheduled(root);
       //立即执行调度任务
      schedulePendingInteractions(root, expirationTime);
      if (executionContext === NoContext) {
        //现在刷新同步工作,除非我们已经在工作或内部
        //一批。这是故意在scheduleUpdateOnFiber而不是
       // scheduleCallbackForFiber来保留调度回调的能力
      //不立即冲洗。我们只对用户发起这样做
       //刷新同步任务队列
       // 函数 3-1-7-9
        flushSyncCallbackQueue();
      }
    }
  } else {
  //如果是异步任务的话,则立即执行调度任务
    ensureRootIsScheduled(root);
    schedulePendingInteractions(root, expirationTime);
  }
  if ((executionContext & DiscreteEventContext) !== NoContext && ( // Only updates at user-blocking priority or greater are considered
  //离散的,即使在离散事件中。
  priorityLevel === UserBlockingPriority$2 || priorityLevel === ImmediatePriority)) {
    //这是一个离散事件的结果。跟踪最低优先级
    //每个根的独立更新,这样我们就可以在需要的时候尽早清除它们。
    if (rootsWithPendingDiscreteUpdates === null) {
      rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
    } else {
      var lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
      if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
        rootsWithPendingDiscreteUpdates.set(root, expirationTime);
      }
    }
  }
}

对于setState的问题

1.setState是否都是异步?如果不是异步,同步与异步在什么场景下区分?

答案:在组件生命周期或React合成事件中,setState是异步;在setTimeout或者原生dom事件中,setState是同步,那么问题来了:相关连接

为什么是异步?

1.保证内部的一致性

​ 即使state同步更新,props也不会。(props直到重新渲染父组件,然后同步执行,批处理才会显示在窗口之外。)

2.启用并发更新

2.setState如何合并

这个问题在setState流程中已经说明

本身以前在工作中很少用setState管理数据,基本都在redux中去统一管理,setState自身的异步状态容易掉坑,虽然可以回调解决,看完源码,setState虽然能合并同一上下文环境多次调用,但队列合并前的流程是没有合并的,还需要多次执行,个人觉得还是慎用。对于react16.12的源码,真心感觉自己太low,好多地方看不明白,暂时记录到这,后期在补充