V15

v15中的整个应用基于事务来实现挂载更新等操作

v15中同一个事物中多次调用setState会被合并为一个state,并对最新的state走一次更新流程。

所有的setState 存放在对象的_pendingStateQueue属性上,在多次调用时:

  1. // 合并state
  2. var nextState = Object.assign({}, replace ? queue[0] : inst.state);
  3. for (var i = replace ? 1 : 0; i < queue.length; i++) {
  4. var partial = queue[i];
  5. Object.assign(
  6. nextState,
  7. typeof partial === 'function'
  8. ? partial.call(inst, nextState, props, context)
  9. : partial,
  10. );
  11. }
  12. // 返回nextState, 浅合并

批量更新,在被react管控的事件中,比如mount、event等类型中产生的setState会被执行批量更新。

全局根据isBatchingUpdates 来进行更新处理,相当于一把锁, 在被管控事件中会锁上,在事务的close阶段把锁打开。


var ReactDefaultBatchingStrategy = {
  isBatchingUpdates: false,

  /**
   * 唯一的更新函数,开启批量更新
   */
  batchedUpdates: function(callback, a, b, c, d, e) {
    var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates;

    ReactDefaultBatchingStrategy.isBatchingUpdates = true;

    if (alreadyBatchingUpdates) {
      return callback(a, b, c, d, e);
    } else {
      return transaction.perform(callback, null, a, b, c, d, e);
    }
  },
};

处于事务中的setState的更新时被管控的,所以会进入一个数组中等待

function enqueueUpdate(component) {
    // 第一次,开启批量更新
  if (!batchingStrategy.isBatchingUpdates) {
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
    // 处于更新中,进入队列中等待
  // dirtyComponents 存放在一次事务中等待被更新的组件实例
  dirtyComponents.push(component);
  if (component._updateBatchNumber == null) {
    component._updateBatchNumber = updateBatchNumber + 1;
  }
}

didmount事件遇到时会加入callbakcQueue中等待,在此次事务的close阶段,会被从后向前执行,也就是先执行子组件的didmount 后执行父组件的

比如在componentDidMount中发起的setState。在初次挂载时react会手动开启批量更新,所以didmount中的setState会被加入的_pendingStateQueue中存放,实例组件进入数组中等待,在手动开启批量更新,发起的事务的close阶段,这些state才会被重新计算后组件更新。

v15中的批量更新由事务来实现,全局的更新由isBatchingUpdates 来管控。 该变量的修改只在首次挂载时和产生事件时调用

V17

在v17中的批量更新分成两种,同步和异步模式。同步模式和v15类似


function App() {

  const [count, setCount] = useState(0)

  const onClick = () => {
    setCount(v => v + 1)
    setCount(v => v + 1)
    setCount(v => v + 1)
    setCount(v => v + 1)
  }

  return (
    <div className="App">
      <p onClick={onClick} >{count}</p>
      aapp
    </div>
  );
}

Sync

function batchedUpdates$1(fn, a) {
  var prevExecutionContext = executionContext;
  executionContext |= BatchedContext; // 增加批量更新的上下文

  try {
    // 执行事件中的回调,也就是dispatchEvent
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;  // 回滚

    if (executionContext === NoContext &&
    !( ReactCurrentActQueue$1.isBatchingLegacy)) { // 执行批量更新
      resetRenderTimer();
      flushSyncCallbacksOnlyInLegacyMode();
    }
  }
}

在其中fn执行是同步执行,也就是事件中异步的调用更新不具有批量更新的优化

Conurrent

比如click事件中触发的更新,属于高优先级更新,执行派发事件时会通过runWithPriority 来执行,scheduler内部会保存这个提供的优先级。在函数内部所获取到的就是依然是同一个优先级

// 该函数回调执行中获取的优先级就是,执行回调传入的优先级
function getCurrentUpdatePriority() {
  return currentUpdatePriority;
}


// 每次开始任务前都会执行返回获取一个当前的lane
function requestUpdateLane(fiber) {

  // 获取当前lane, 也就是currentUpdatePriority, 
  // 这个变量在执行之前已经被赋值过了也就是事件中的优先级
  var updateLane = getCurrentUpdatePriority();

  // 如果update可用,就返回,在一次click事件处理函数中,总是会走到这里返回
  if (updateLane !== NoLane) {
    return updateLane;
  }

  var eventLane = getCurrentEventPriority();
  return eventLane;
}

// 也就是同一个执行上下文里面获取的lane相同,会被执行批量更新

事件派发在react中被统一管理,触发事件时会在fiber链表中执行对应的回调函数来模拟事件冒泡
但是在派发事件前会做一部分工作

// 在派发一次事件时
function dispatchDiscreteEvent(domEventName, eventSystemFlags, container, nativeEvent) {
  // 获取当前的优先级 为0也就是NoLane
  var previousPriority = getCurrentUpdatePriority();
    // ...

  try {
    // 设置react的优先级会改写currentUpdatePriority变量
    setCurrentUpdatePriority(DiscreteEventPriority);
    // 派发事件
    dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
  } finally {
    // 回滚
    setCurrentUpdatePriority(previousPriority);
  }
}

走到这里,一次事件中的优先级是同样的,所以后面的setState获取的优先级就是现在给的



function ensureRootIsScheduled(root, currentTime) {
  var existingCallbackNode = root.callbackNode;

  // 获取 `react` 当前优先级最高的lanes, 也就是当前上下文中可以使用的lanes
  var nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes
  );

    // ...

    // 使用最高优先级 置换 scheduler中的优先级
  var newCallbackPriority = getHighestPriorityLane(nextLanes);

  var existingCallbackPriority = root.callbackPriority;
    // 当前新任务和正在执行的任务优先级相同就返回
  // 优先级相同代表lane相同,lane相同代表所处的上下文相同
  if (existingCallbackPriority === newCallbackPriority &&
  !( ReactCurrentActQueue$1.current !== null  && 
    existingCallbackNode !== fakeActCallbackNode)
 ) {
    return;
  }
    // 如果新任务的优先级高于正在执行任务的优先级,就取消旧任务,重新加入新任务的回调
  if (existingCallbackNode != null) {
    cancelCallback$1(existingCallbackNode);
  }

  var newCallbackNode; // scheduleSyncCallback 调度返回的任务,同步、异步、blocking模式
    // ... 略
  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

在异步模式下,基于lane模型的更新实现,在异步回调函数中的更新也可以得到批量更新

所以在执行时所在环境的优先级相同,最后得到的sheduler优先级相同,在开始调度时都只会执行第一次。

基于Lane的更新

基于lane中的更新是固定的,比如click就是userBlocking的优先级,同样的优先级回返回同样的lane

现在批量更新是处于同一个上下文中的更新会被合并,因为优先级一样,lane一样
如果是在更新中又发起的更新,可能会根据message事件获取eventLane,比如在Transtion上下文中。这种就会被合并