react-dom 源码(3)updateContainer

1,889 阅读12分钟

unbatchedUpdates

// 回顾调用参数
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
  //...
  var root = container._reactRootContainer;
  var fiberRoot;

  if (!root) {
    // Initial mount
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
    fiberRoot = root._internalRoot;
    //...
    // Initial mount should not be batched.
    unbatchedUpdates(function () {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    fiberRoot = root._internalRoot;
    //...
    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }

  return getPublicRootInstance(fiberRoot);
}
var NoContext = /*             0b000000*/0;
var BatchedContext = /*        0b000001*/1;
var EventContext = /*          0b000010*/2;
var DiscreteEventContext = /*  0b000100*/4;
var LegacyUnbatchedContext = /*0b001000*/8;
var RenderContext = /*         0b010000*/16;
var CommitContext = /*         0b100000*/32;

// Describes where we are in the React execution stack
//描述我们在React执行栈中的位置 
var executionContext = NoContext; // The root we're working on
function unbatchedUpdates(fn, a) {
  var prevExecutionContext = executionContext;
  executionContext &= ~BatchedContext;
  executionContext |= LegacyUnbatchedContext;

  try {
    return fn(a);
  } finally {
    executionContext = prevExecutionContext;

    if (executionContext === NoContext) {
      // Flush the immediate callbacks that were scheduled during this batch
      //刷新同步回调队列
      flushSyncCallbackQueue();
    }
  }
}

源码说明:

  1. 将执行上下文(executionContext)切换成 LegacyUnbatchedContext (非批量上下文);
  2. 调用 updateContainer 执行更新操作;
  3. 将执行上下文(executionContext)恢复到之前的状态;
  4. 如果之前的执行上下文是 NoContext,则调用 flushSyncCallbackQueue 刷新同步回调队列。

flushSyncCallbackQueue 跟调度相关,放到 updateContainer 之后,任务调度中详解。

updateContainer

// 回顾一下调用栈:render > legacyRenderSubtreeIntoContainer > updateContainer
// 传参:updateContainer(children, fiberRoot, parentComponent, callback);
function updateContainer(element, container, parentComponent, callback) {
  // fiberRoot.current 即 rootFiber
  var current?1 = container.current;
  // 计算进行更新的当前时间
  var currentTime = requestCurrentTimeForUpdate();

  {
    if ('undefined' !== typeof jest) {
      warnIfUnmockedScheduler(current?1);
      warnIfNotScopedWithMatchingAct(current?1);
    }
  }

  var suspenseConfig = requestCurrentSuspenseConfig();
  // 获取 fiber 的任务到期时间
  var expirationTime = computeExpirationForFiber(currentTime, current?1, suspenseConfig);

  {
    if (ReactFiberInstrumentation_1.debugTool) {
      if (current?1.alternate === null) {
        ReactFiberInstrumentation_1.debugTool.onMountContainer(container);
      } else if (element === null) {
        ReactFiberInstrumentation_1.debugTool.onUnmountContainer(container);
      } else {
        ReactFiberInstrumentation_1.debugTool.onUpdateContainer(container);
      }
    }
  }

  var context = getContextForSubtree(parentComponent);

  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  {
    if (phase === 'render' && current !== null && !didWarnAboutNestedUpdates) {
      didWarnAboutNestedUpdates = true;
      warningWithoutStack$1(false, 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentName(current.type) || 'Unknown');
    }
  }

  var update = createUpdate(expirationTime, suspenseConfig); // Caution: React DevTools currently depends on this property
  // being called "element".

  update.payload = {
    element: element
  };
  callback = callback === undefined ? null : callback;

  if (callback !== null) {
    !(typeof callback === 'function') ? warningWithoutStack$1(false, 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback) : void 0;
    update.callback = callback;
  }

  enqueueUpdate(current?1, update);
  scheduleWork(current?1, expirationTime);
  return expirationTime;
}

源码说明

  1. 拿到 fiberRoot.current 即 rootFiber;
  2. 调用 requestCurrentTimeForUpdate 计算进行更新的当前时间;
  3. 调用 requestCurrentSuspenseConfig 计算当前 Suspense 配置;
  4. 调用 computeExpirationForFiber 计算 fiber 的到期时间 expirationTime;
  5. 调用 getContextForSubtree 获取子树的上下文 context;
  6. 调用 createUpdate 创建一个 update;
  7. 将 update 的 payload 设置为 element(即传入的 children,也就是 render 调用时传入的 react 组件),如果 render 的时候有 callback 回调参数,将其设置在 update.callback 上;
  8. 调用 enqueueUpdate 将更新放到更新队列(即 rootFiber.updateQueue);
  9. 调用 scheduleWork 执行任务调度;
  10. 返回到期时间 expirationTime;

里面使用了 currentTime 和 expirationTime:

  • currentTime 是用来计算 expirationTime 的;
  • expirationTime 即过期时间,代表着优先级,后面会分析。

requestCurrentTimeForUpdate

注释翻译

  • 如果初始时间戳相当小,请直接使用Scheduler的now
  • 对于支持performance.now的现代浏览器来说就是这种情况。
  • 在较旧的浏览器,Scheduler退回到Date.now,它返回一个Unix时间戳记。
  • 在这种情况下,减去模块初始化时间即可进行仿真Performance.now的行为,
  • 并保持足够小的时间以适应不超过32位。
var NoContext = /*             0b000000*/0;
var BatchedContext = /*        0b000001*/1;
var EventContext = /*          0b000010*/2;
var DiscreteEventContext = /*  0b000100*/4;
var LegacyUnbatchedContext = /*0b001000*/8;
var RenderContext = /*         0b010000*/16;
var CommitContext = /*         0b100000*/32;
// Describes where we are in the React execution stack
//描述我们在React执行栈中的位置 
var executionContext = NoContext; // The root we're working on

var initialTimeMs = Scheduler_now(); 
var now = initialTimeMs < 10000 ? Scheduler_now : function () {
  return Scheduler_now() - initialTimeMs;
};

executionContext 初始值是 NoContext,但由于先进入 Init mount 阶段, unbatchedUpdates 中已将其设置为 LegacyUnbatchedContext。

注释翻译

  • 到期时间是通过加到当前时间(开始时间)来计算的;
  • 但是,如果在同一事件中安排了两次更新,则我们会将其开始时间视为同时的,即使介于第一次更新和第二次更新之间有一定的实际时钟间隔;
  • 换句话说,由于到期时间决定了更新的批处理方式,所以我们希望同一事件中发生的所有优先级相同的更新收到相同的到期时间。
var currentEventTime = NoWork;
function requestCurrentTimeForUpdate() {
  if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
    // We're inside React, so it's fine to read the actual time.
    return msToExpirationTime(now());
  } 
  
  // We're not inside React, so we may be in the middle of a browser event.
  if (currentEventTime !== NoWork) {
    // Use the same start time for all updates until we enter React again.
    return currentEventTime;
  } // This is the first update since React yielded. Compute a new start time.


  currentEventTime = msToExpirationTime(now());
  return currentEventTime;
}

源码说明

这里面有三种情况:

  1. 当处于 RenderPhase 或 CommitPhase 阶段时,需要返回实际时间;
  2. 如果是不处于 RenderPhase 和 CommitPhase 阶段,且 currentEventTime 不等于 NoWork(0),说明我们不在 React 内部,我们可能处于浏览器事件中(??),需要对所有更新使用相同的开始时间,直到我们再次进入 React 环境,因此直接返回之前的时间;
  3. 如果 currentEventTime 处于 NoWork 阶段,说明是初次更新,计算当前活动时间并返回;

因此通过 requestCurrentTimeForUpdate 拿到的 currentTime 只有2种情况:要么比之前大,要么与之前相同。

now()

var Scheduler_now = Scheduler.unstable_now;


if ( typeof window === 'undefined' || typeof MessageChannel !== 'function') {
  //...
  
  var initialTime = Date.now();
  exports.unstable_now = function () {
    return Date.now() - initialTime;
  };
} else {
  // Capture local references to native APIs, in case a polyfill overrides them.
  //获取对本地API的本地引用,以防polyfill覆盖它们。 
  var performance = window.performance;
  var _Date = window.Date;
  var _setTimeout = window.setTimeout;
  var _clearTimeout = window.clearTimeout;
  //...
  
  if (typeof performance === 'object' && typeof performance.now === 'function') {
    exports.unstable_now = function () {
      return performance.now();
    };
  } else {
    var _initialTime = _Date.now();
    exports.unstable_now = function () {
      return _Date.now() - _initialTime;
    };
  }
}

解释见 scheduler 源码

msToExpirationTime

注释翻译

  • Never 与 Idle 的主要区别在于:Never 任务可以在状态不一致的情况下,不破坏 UI 地进行 commit;常见情况是屏外内容,例如隐藏的子树。
  • Idle 的优先级比 Never 高。它必须按顺序完全完成。
  • ContinuousHydration 是一个变化的优先级,比 Idle 略高,用于增加悬停目标的优先级。每使用一次都会加1,所以总会使最后一次获胜。
var NoWork = 0; 
var Never = 1;
var Idle = 2;
var ContinuousHydration = 3;
// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// Math.pow(2, 30) - 1
// 0b111111111111111111111111111111
var MAX_SIGNED_31_BIT_INT = 1073741823;
var Sync = MAX_SIGNED_31_BIT_INT;
var Batched = Sync - 1;
var UNIT_SIZE = 10; // 1个单位的有效时间代表10ms
var MAGIC_NUMBER_OFFSET = Batched - 1; 

function msToExpirationTime(ms) {
  //始终添加一个偏移量,以免与NoWork(0)的魔幻数字发生冲突。 
  return MAGIC_NUMBER_OFFSET - (ms / UNIT_SIZE | 0);
}

function computeContinuousHydrationExpiration(currentTime) {
  // Each time we ask for a new one of these we increase the priority.
  // This ensures that the last one always wins since we can't deprioritize
  // once we've scheduled work already.
  return ContinuousHydration++;
}

源码说明

  • 这个公式只有一个变量ms,其他都是常量,因此传入的ms越大,msToExpirationTime的返回值越小。
  • 这里的 | 0 作用是取整数;
  • MAGIC_NUMBER_OFFSET 的作用不太明白??

computeExpirationForFiber

var NoMode = 0;
var StrictMode = 1; 
// TODO: Remove BlockingMode and ConcurrentMode by reading from the root tag instead
var BlockingMode = 2;
var ConcurrentMode = 4;
var ProfileMode = 8;
function computeExpirationForFiber(currentTime, fiber, suspenseConfig) {
  var mode = fiber.mode;

  if ((mode & BlockingMode) === NoMode) {
    return Sync;
  }

  var priorityLevel = getCurrentPriorityLevel();

  if ((mode & ConcurrentMode) === NoMode) {
    return priorityLevel === ImmediatePriority ? Sync : Batched;
  }

  if ((executionContext & RenderContext) !== NoContext) {
    // Use whatever time we're already rendering
    // TODO: Should there be a way to opt out, like with `runWithPriority`?
    return renderExpirationTime;
  }

  var expirationTime;

  if (suspenseConfig !== null) {
    // Compute an expiration time based on the Suspense timeout.
    //根据Suspense超时计算过期时间。
    expirationTime = computeSuspenseExpiration(currentTime, suspenseConfig.timeoutMs | 0 || LOW_PRIORITY_EXPIRATION);
  } else {
    // Compute an expiration time based on the Scheduler priority.
    //根据Scheduler优先级计算到期时间。 
    switch (priorityLevel) {
      case ImmediatePriority:
        expirationTime = Sync;
        break;

      case UserBlockingPriority$2:
        // TODO: Rename this to computeUserBlockingExpiration
        expirationTime = computeInteractiveExpiration(currentTime);
        break;

      case NormalPriority:
      case LowPriority:
        // TODO: Handle LowPriority
        // TODO: Rename this to... something better.
        expirationTime = computeAsyncExpiration(currentTime);
        break;

      case IdlePriority:
        expirationTime = Idle;
        break;

      default:
        {
          {
            throw Error("Expected a valid priority level");
          }
        }

    }
  } 
  // If we're in the middle of rendering a tree, do not update at the same
  // expiration time that is already rendering.
  //如果正在渲染一棵树,请不要同时进行更新
  if (workInProgressRoot !== null && expirationTime === renderExpirationTime) {
    // This is a trick to move this update into a separate batch
    expirationTime -= 1;
  }

  return expirationTime;
}

源码说明

  1. 获取 rootFiber 上的 mode 属性;
  2. 调用 getCurrentPriorityLevel 获取当前优先级;
  3. 如果 suspenseConfig 存在,则调用 computeSuspenseExpiration 设置到期时间;
  4. 当 suspenseConfig 不存在,根据优先级 priorityLevel 设置到期时间
    1. 立即更新(ImmediatePriority):Sync
    2. 用户交互更新(UserBlockingPriority$2):调用 computeInteractiveExpiration 计算;
    3. 普通更新与低优先级(NormalPriority、LowPriority):调用 computeAsyncExpiration 计算;
    4. 空闲优先级(IdlePriority):Idle
    5. default:报错
  5. 返回 expirationTime 到期时间。

当前 fiber 是 rootFiber,rootFiber.mode 是 NoMode,所以直接返回 Sync;

expirationTime 是到期时间,在 react 中用来标识优先级,数字越大优先级越高。sync 的数字是最大的,所以优先级也是最高的。

getCurrentPriorityLevel

var ImmediatePriority = 99;
var UserBlockingPriority$2 = 98;
var NormalPriority = 97;
var LowPriority = 96;
var IdlePriority = 95; 
var NoPriority = 90;

var Scheduler_getCurrentPriorityLevel = Scheduler.unstable_getCurrentPriorityLevel;

function unstable_getCurrentPriorityLevel() {
  return currentPriorityLevel;
}
function getCurrentPriorityLevel() {
  switch (Scheduler_getCurrentPriorityLevel()) {
    case Scheduler_ImmediatePriority:
      return ImmediatePriority;

    case Scheduler_UserBlockingPriority:
      return UserBlockingPriority$2;

    case Scheduler_NormalPriority:
      return NormalPriority;

    case Scheduler_LowPriority:
      return LowPriority;

    case Scheduler_IdlePriority:
      return IdlePriority;

    default:
      {
        {
          throw Error("Unknown priority level.");
        }
      }

  }
}

源码说明

  1. Scheduler_getCurrentPriorityLevel 涉及 Scheduler 内容,获取到当前优先级;
  2. 将 Scheduler 中优先级与 React 中优先级对应并返回。

计算到期时间

获取到期时间都调用了 computeExpirationBucket,只是传入不一样参数,以区分优先级高低,调用如下:

我们有意在开发环境,将交互式更新设置为更长的到期时间,以帮助开发者发现调度问题并解决;而在生产环境,我们会选择更好的用户体验,通过快速调度掩盖有调度风险的问题

// LOW 低优先级任务
// 这其实对应于调度程序的NormalPriority,而不是LowPriority。
var LOW_PRIORITY_EXPIRATION = 5000;
var LOW_PRIORITY_BATCH_SIZE = 250;
function computeAsyncExpiration(currentTime) {
  return computeExpirationBucket(currentTime, LOW_PRIORITY_EXPIRATION, LOW_PRIORITY_BATCH_SIZE);
}

// HIGH 高优先级任务
var HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
var HIGH_PRIORITY_BATCH_SIZE = 100;
function computeInteractiveExpiration(currentTime) {
  return computeExpirationBucket(currentTime, HIGH_PRIORITY_EXPIRATION, HIGH_PRIORITY_BATCH_SIZE);
}

function computeSuspenseExpiration(currentTime, timeoutMs) {
  return computeExpirationBucket(currentTime, timeoutMs, LOW_PRIORITY_BATCH_SIZE);
}

function computeContinuousHydrationExpiration(currentTime) {
  // Each time we ask for a new one of these we increase the priority.
  // This ensures that the last one always wins since we can't deprioritize
  // once we've scheduled work already.
  return ContinuousHydration++;
}

computeExpirationBucket

// Max 31 bit integer. The max integer size in V8 for 32-bit systems.
// Math.pow(2, 30) - 1
// 0b111111111111111111111111111111
var MAX_SIGNED_31_BIT_INT = 1073741823;
var Sync = MAX_SIGNED_31_BIT_INT;
var Batched = Sync - 1;
var UNIT_SIZE = 10; // 1个单位的有效时间代表10ms
var MAGIC_NUMBER_OFFSET = Batched - 1; 
function ceiling(num, precision) {
  return ((num / precision | 0) + 1) * precision;
}

function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs) {
  return MAGIC_NUMBER_OFFSET - ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE);
}

computeExpirationBucket 的返回值比较复杂,以 computeAsyncExpiration 为例,可以简化一下:

// 简化
return 1073741822 - ceiling((1073741822 - currentTime + 500), 25);

//进一步简化
1073741822  - ((((1073741822 - currentTime + 500)/ 25) | 0) + 1) * 25;

| 0 的意思是取整(不作四舍五入),所以整个返回值的意思是:取得(1073741822 - currentTime + 500)/ 25(简称A值)的整数部分再加1,最后乘以25。这就有了一个问题,为什么要取整。react这么做,其实是为了合并更新任务,提高效率。如果在一个地方多次调用setState,那么他们的currentTime虽然可能极其接近,但是也不相同,于是计算出来的expirationTime也不同,就要进行不同的更新任务,那么效率就会变低,于是react就让合并比较时间比较接近的更新任务,即:让它们的expirationTime保持一致。

源码说明

  • 关键点在于 ceiling 函数的 num / precision | 0 除精度后取整操作,使得 precision 精度范围内的时间变化取得同一个值,所以得到的 expirationTime 是同一个值,所以:
    • Async 任务的时间周期是 25ms;
    • Interactive 任务的时间周期是 10ms;
  • 忽略取整操作,公式简化后 computeInteractiveExpiration 在 DEV 环境下,返回的是 currentTime - 60,production 环境下,返回的是 currentTime - 25;而 computeAsyncExpiration 返回的是 currentTime - 525,所以在 currentTime 相同的情况下,Interactive 返回的值更大,所以 Interactive 优先级高于 Async
  • 同时可以得出,currentTime 越大,expirationTime 越大。

getContextForSubtree

// 回顾一下调用栈:render > legacyRenderSubtreeIntoContainer > updateContainer > getContextForSubtree
var emptyContextObject = {};

function getContextForSubtree(parentComponent) {
  if (!parentComponent) {
    return emptyContextObject;
  }

  var fiber = get(parentComponent);
  var parentContext = findCurrentUnmaskedContext(fiber);

  if (fiber.tag === ClassComponent) {
    var Component = fiber.type;

    if (isContextProvider(Component)) {
      return processChildContext(fiber, Component, parentContext);
    }
  }

  return parentContext;
}

源码说明

  • 传参 parentComponent 其实就是 render 函数的第一个参数,即 null,所以直接返回一个空对象;
  • 只有使用 unstable_renderSubtreeIntoContainer 调用时,parentComponent 才会有值,这里暂不分析。

创建更新 createUpdate

React 的状态更新分为四种情况,他们分别对应 Update 的 tag 属性的四个值:

var UpdateState = 0;
var ReplaceState = 1;
var ForceUpdate = 2;
var CaptureUpdate = 3;
function createUpdate(expirationTime, suspenseConfig) {
  var update = {
    expirationTime: expirationTime,
    suspenseConfig: suspenseConfig,
    tag: UpdateState,
    payload: null,
    callback: null,
    next: null,
    nextEffect: null
  };

  {
    update.priority = getCurrentPriorityLevel();
  }

  return update;
}

源码说明

Update 的数据结构:

  • expirationTime:到期时间;
  • suspenseConfig:suspense 配置;
  • tag:表示 update 的更新状态,有 0、1、2、3 四个值,分别表示更新、替换、强制更新、捕获;
  • payload:需要更新的内容;
  • callback:回调函数,如 render,setState 对应的回调;
  • next:指向下一个 update 的"指针" ;
  • nextEffect:指向下一个 effect(变化)。

更新队列

createUpdateQueue

function createUpdateQueue(baseState) {
  var queue = {
    baseState: baseState,
    firstUpdate: null,
    lastUpdate: null,
    firstCapturedUpdate: null,
    lastCapturedUpdate: null,
    firstEffect: null,
    lastEffect: null,
    firstCapturedEffect: null,
    lastCapturedEffect: null
  };
  return queue;
}

源码说明

UpdateQueue 的数据结构

  • baseState: 每次操作完更新之后的state
  • firstUpdate: 队列中的第一个Update
  • lastUpdate: 队列中的最后一个Update
  • firstCapturedUpdate: 第一个捕获类型的Update
  • lastCapturedUpdate: 最后一个捕获类型的Update
  • firstEffect: 第一个side effect
  • lastEffect: 最后一个side effect
  • firstCapturedEffect: 第一个捕获产生的side effect
  • lastCapturedEffect: 最后一个捕获产生的side effect

cloneUpdateQueue

// 克隆一个队列
function cloneUpdateQueue(currentQueue) {
  var queue = {
    baseState: currentQueue.baseState,
    firstUpdate: currentQueue.firstUpdate,
    lastUpdate: currentQueue.lastUpdate,
    // TODO: With resuming, if we bail out and resuse the child tree, we should
    // keep these effects.
    firstCapturedUpdate: null,
    lastCapturedUpdate: null,
    firstEffect: null,
    lastEffect: null,
    firstCapturedEffect: null,
    lastCapturedEffect: null
  };
  return queue;
}

appendUpdateToQueue

// 将 update 追加到链表末尾,并更新 queue.lastUpdate 指针
function appendUpdateToQueue(queue, update) {
  // Append the update to the end of the list.
  if (queue.lastUpdate === null) {
    // Queue is empty
    queue.firstUpdate = queue.lastUpdate = update;
  } else {
    queue.lastUpdate.next = update;
    queue.lastUpdate = update;
  }
}

enqueueUpdate

function enqueueUpdate(fiber, update) {
  // Update queues are created lazily.
  var alternate = fiber.alternate;
  var queue1;
  var queue2;

  if (alternate === null) {
    // There's only one fiber.
    queue1 = fiber.updateQueue;
    queue2 = null;

    if (queue1 === null) {
      queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
    }
  } else {
    // There are two owners.
    queue1 = fiber.updateQueue;
    queue2 = alternate.updateQueue;

    if (queue1 === null) {
      if (queue2 === null) {
        // Neither fiber has an update queue. Create new ones.
        queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
        queue2 = alternate.updateQueue = createUpdateQueue(alternate.memoizedState);
      } else {
        // Only one fiber has an update queue. Clone to create a new one.
        queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
      }
    } else {
      if (queue2 === null) {
        // Only one fiber has an update queue. Clone to create a new one.
        queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
      } else {// Both owners have an update queue.
      }
    }
  }

  if (queue2 === null || queue1 === queue2) {
    // There's only a single queue.
    appendUpdateToQueue(queue1, update);
  } else {
    // There are two queues. We need to append the update to both queues,
    // while accounting for the persistent structure of the list — we don't
    // want the same update to be added multiple times.
    if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
      // One of the queues is not empty. We must add the update to both queues.
      appendUpdateToQueue(queue1, update);
      appendUpdateToQueue(queue2, update);
    } else {
      // Both queues are non-empty. The last update is the same in both lists,
      // because of structural sharing. So, only append to one of the lists.
      appendUpdateToQueue(queue1, update); // But we still need to update the `lastUpdate` pointer of queue2.

      queue2.lastUpdate = update;
    }
  }

  {
    if (fiber.tag === ClassComponent && (currentlyProcessingQueue === queue1 || queue2 !== null && currentlyProcessingQueue === queue2) && !didWarnUpdateInsideUpdate) {
      warningWithoutStack$1(false, 'An update (setState, replaceState, or forceUpdate) was scheduled ' + 'from inside an update function. Update functions should be pure, ' + 'with zero side-effects. Consider using componentDidUpdate or a ' + 'callback.');
      didWarnUpdateInsideUpdate = true;
    }
  }
}

源码说明

alternate 为 fiber.alernate;q1 代表 fiber.updateQueue,q2 代表 fiber.alternate.updateQueue alternate 主要用来保存更新过程中各版本更新队列,方便崩溃或冲突时回退

  • 获取 fiber 上的 alternate 属性;
  • 根据 alernate、q1、q2 的状态,构建 q1、q2
    1. alernate 为空(当前只有一个 fiber),将 q2 置为 null(没有 alernate 为空,q2 非空的情况)
      1. q1 为空,根据 fiber.memoizedState 新建 q1 队列;
      2. q1 非空,不处理;
    2. alernate 非空(有两个 fiber)
      1. q1、q2 为空,分别根据 fiber 和 alternate 的 memoizedState 属性新建 q1、q2 队列;
      2. q1 为空,q2 非空,根据 fiber.memoizedState 新建 q1 队列;
      3. q1 非空,q2 为空,克隆 q1 的值到 q2;
      4. q1、q2 均非空,不处理。
  • ==根据 q1、q2 的状态,判断如何更新 q1、q2==
    1. 当 q2 为 null 或等于 q1,即 i、ii(a、c) 的情况,此时只有一个队列,只需将 update 追加到 q1;
    2. 当 q2 不为 null 且不等于 q1,此时有两个队列
      1. 如果任一个队列中没有节点:我们必须将 update 添加到两个队列中。
      2. 如果两个队列都有节点:由于结构共享,两个队列中的最新更新相同,所以只需要追加到 q1,同时将 q2 的 lastUpdate 指针指向当前 update。

scheduleWork

scheduleWork 跟调度相关,放到下一章任务调度中详解。