React 16.8.0 初次渲染 源码分析(二)

书接上文,从legacyRenderSubtreeIntoContainer - unbatchedUpdates看起
// Initial mount should not be batched.
unbatchedUpdates(() => {
    // parentComponent = null
    if (parentComponent != null) {
      root.legacy_renderSubtreeIntoContainer(
        parentComponent,
        children,
        callback,
      );
    } else {
      root.render(children, callback);
    }
});
legacyRenderSubtreeIntoContainer - unbatchedUpdates
  • packages/react-reconciler/src/ReactFiberScheduler.js
  • 全局变量 
  1. nextFlushedRoot
  2. nextFlushedExpirationTime
  3. currentSchedulerTime
  4. currentRendererTime
// TODO: Batching should be implemented at the renderer level, not inside
// the reconciler.
function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
  if (isBatchingUpdates && !isUnbatchingUpdates) {
    isUnbatchingUpdates = true;
    try {
      return fn(a);
    } finally {
      isUnbatchingUpdates = false;
    }
  }
  return fn(a);
}
unbatchedUpdates - 非批量update
t.render
export function updateContainer(
    element: ReactNodeList, // <List />,
    container: OpaqueRoot, // FiberRoot
    parentComponent: ?React$Component<any, any>, // null
    callback: ?Function, // this._onCommit.bind(this)
  ): ExpirationTime {
    const current = container.current; // FiberNode
    const currentTime = requestCurrentTime();
    const expirationTime = computeExpirationForFiber(currentTime, current);
    return updateContainerAtExpirationTime(
      element,
      container,
      parentComponent,
      expirationTime,
      callback,
    );
  }
updateContainer
function requestCurrentTime() {
// requestCurrentTime计算过期时间。
// 过期时间是通过将当前时间(开始时间)相加来计算的。
// 但是,如果在同一事件中安排了两个更新,则应将它们的开始时间视为同时进行,即使实际时钟时间在第一次和第二次调用之间提前。
// 换句话说,因为过期时间决定了更新的批处理方式,
// 我们希望在同一事件中发生的所有具有相同优先级的更新接收相同的过期时间。否则我们会被撕裂。
// 我们跟踪两个不同的时间:当前“渲染器”时间和当前“调度程序”时间。渲染器时间可以随时更新;它的存在只是为了最小化调用性能。
// 但是,只有在没有挂起的工作,或者我们确定自己没有处于事件的中间时,调度程序时间才能被更新。
  
    if (isRendering) {
      // We're already rendering. Return the most recently read time.
      return currentSchedulerTime;
    }
    // Check if there's pending work.
    findHighestPriorityRoot();
    if (
      nextFlushedExpirationTime === NoWork ||
      nextFlushedExpirationTime === Never
    ) {
      // If there's no pending work, or if the pending work is offscreen, we can
      // read the current time without risk of tearing.
      // 如果没有挂起的工作,或者挂起的工作在屏幕外,我们可以读取当前时间而不会有撕裂的风险。
      recomputeCurrentRendererTime();
      currentSchedulerTime = currentRendererTime;
      return currentSchedulerTime;
    }
    // There's already pending work. We might be in the middle of a browser
    // event. If we were to read the current time, it could cause multiple updates
    // within the same event to receive different expiration times, leading to
    // tearing. Return the last read time. During the next idle callback, the
    // time will be updated.
    // 已经有工作要做了。我们可能正在浏览器事件中。
    // 如果我们要读取当前时间,它可能会导致同一事件中的多个更新接收到不同的过期时间,从而导致撕裂。
    // 返回上次读取时间。在下一次空闲回调期间,时间将被更新。
    return currentSchedulerTime;
  }
requestCurrentTime - 计算过期时间
function findHighestPriorityRoot() {
  let highestPriorityWork = NoWork;
  let highestPriorityRoot = null;
  // lastScheduledRoot  = null
  if (lastScheduledRoot !== null) {
    let previousScheduledRoot = lastScheduledRoot;
    let root = firstScheduledRoot;
    while (root !== null) {
      const remainingExpirationTime = root.expirationTime;
      if (remainingExpirationTime === NoWork) {
        // This root no longer has work. Remove it from the scheduler.

        // TODO: This check is redudant, but Flow is confused by the branch
        // below where we set lastScheduledRoot to null, even though we break
        // from the loop right after.
        invariant(
          previousScheduledRoot !== null && lastScheduledRoot !== null,
          'Should have a previous and last root. This error is likely ' +
            'caused by a bug in React. Please file an issue.',
        );
        if (root === root.nextScheduledRoot) {
          // This is the only root in the list.
          root.nextScheduledRoot = null;
          firstScheduledRoot = lastScheduledRoot = null;
          break;
        } else if (root === firstScheduledRoot) {
          // This is the first root in the list.
          const next = root.nextScheduledRoot;
          firstScheduledRoot = next;
          lastScheduledRoot.nextScheduledRoot = next;
          root.nextScheduledRoot = null;
        } else if (root === lastScheduledRoot) {
          // This is the last root in the list.
          lastScheduledRoot = previousScheduledRoot;
          lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
          root.nextScheduledRoot = null;
          break;
        } else {
          previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
          root.nextScheduledRoot = null;
        }
        root = previousScheduledRoot.nextScheduledRoot;
      } else {
        if (remainingExpirationTime > highestPriorityWork) {
          // Update the priority, if it's higher
          highestPriorityWork = remainingExpirationTime;
          highestPriorityRoot = root;
        }
        if (root === lastScheduledRoot) {
          break;
        }
        if (highestPriorityWork === Sync) {
          // Sync is highest priority by definition so
          // we can stop searching.
          break;
        }
        previousScheduledRoot = root;
        root = root.nextScheduledRoot;
      }
    }
  }

  nextFlushedRoot = highestPriorityRoot;
  nextFlushedExpirationTime = highestPriorityWork;
}
findHighestPriorityRoot - 查找最高优先级root
function recomputeCurrentRendererTime() {
    const currentTimeMs = now() - originalStartTimeMs;
    currentRendererTime = msToExpirationTime(currentTimeMs);
}

const UNIT_SIZE = 10;
const MAX_SIGNED_31_BIT_INT = 1073741823;
const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;
// 1 unit of expiration time represents 10ms.
export function msToExpirationTime(ms: number): ExpirationTime {
    // Always add an offset so that we don't clash with the magic number for NoWork.
    return MAGIC_NUMBER_OFFSET - ((ms / UNIT_SIZE) | 0);
}
recomputeCurrentRendererTime - 重新计算当前渲染时间
computeExpirationForFiber返回一个Sync
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
  let expirationTime;
  if (expirationContext !== NoWork) {
    // An explicit expiration context was set;
    expirationTime = expirationContext;
  } else if (isWorking) {
    if (isCommitting) {
      // Updates that occur during the commit phase should have sync priority
      // by default.
      expirationTime = Sync;
    } else {
      // Updates during the render phase should expire at the same time as
      // the work that is being rendered.
      expirationTime = nextRenderExpirationTime;
    }
  } else {
    // No explicit expiration context was set, and we're not currently
    // performing work. Calculate a new expiration time.
    if (fiber.mode & ConcurrentMode) {
      if (isBatchingInteractiveUpdates) {
        // This is an interactive update
        expirationTime = computeInteractiveExpiration(currentTime);
      } else {
        // This is an async update
        expirationTime = computeAsyncExpiration(currentTime);
      }
      // If we're in the middle of rendering a tree, do not update at the same
      // expiration time that is already rendering.
      if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
        expirationTime -= 1;
      }
    } else {
      // This is a sync update
      expirationTime = Sync;
    }
  }
  if (isBatchingInteractiveUpdates) {
    // This is an interactive update. Keep track of the lowest pending
    // interactive expiration time. This allows us to synchronously flush
    // all interactive updates when needed.
    if (
      lowestPriorityPendingInteractiveExpirationTime === NoWork ||
      expirationTime < lowestPriorityPendingInteractiveExpirationTime
    ) {
      lowestPriorityPendingInteractiveExpirationTime = expirationTime;
    }
  }
  return expirationTime;
}
computeExpirationForFiber - 计算当前fiber节点到期时间
export function updateContainerAtExpirationTime(
  element: ReactNodeList,// <List />,
  container: OpaqueRoot,// FiberRoot
  parentComponent: ?React$Component<any, any>,// null
  expirationTime: ExpirationTime, // Sync
  callback: ?Function, // this._onCommit.bind(this)
) {
  // TODO: If this is a nested container, this won't be the root.
  const current = container.current; // FiberNode

  const context = getContextForSubtree(parentComponent); // context = {}
  // container.context = null
  if (container.context === null) {
    container.context = context; // container.context = {}
  } else {
    container.pendingContext = context;
  }

  return scheduleRootUpdate(current, element, expirationTime, callback);
}
updateContainerAtExpirationTime - 过期时 update container 
  • packages/react-reconciler/src/ReactUpdateQueue.js
export function createUpdate(expirationTime: ExpirationTime): Update<*> {
  return {
    expirationTime: expirationTime,

    tag: UpdateState, // 0
    payload: null,
    callback: null,

    next: null,
    nextEffect: null,
  };
}
createUpdate - 创建 Update 对象
function scheduleRootUpdate(
  current: Fiber, // FiberNode
  element: ReactNodeList, // <List />
  expirationTime: ExpirationTime, // Sync
  callback: ?Function, // this._onCommit.bind(this)
) {
  const update = createUpdate(expirationTime);
  // Caution: React DevTools currently depends on this property
  // being called "element".
  update.payload = {element};

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

  flushPassiveEffects();
  enqueueUpdate(current, update);
  scheduleWork(current, expirationTime);

  return expirationTime;
}
scheduleRootUpdate - 计划 root update
function flushPassiveEffects() {
  if (passiveEffectCallbackHandle !== null) {
    cancelPassiveEffects(passiveEffectCallbackHandle);
  }
  if (passiveEffectCallback !== null) {
    // We call the scheduled callback instead of commitPassiveEffects directly
    // to ensure tracing works correctly.
    passiveEffectCallback();
  }
}
flushPassiveEffects - 渲染被动Effects
  • packages/react-reconciler/src/ReactUpdateQueue.js
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
  // Update queues are created lazily.
  const alternate = fiber.alternate;
  let queue1;
  let 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;
    }
  }
}
enqueueUpdate - 排队更新
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
  const queue: UpdateQueue<State> = {
    baseState,
    firstUpdate: null,
    lastUpdate: null,
    firstCapturedUpdate: null,
    lastCapturedUpdate: null,
    firstEffect: null,
    lastEffect: null,
    firstCapturedEffect: null,
    lastCapturedEffect: null,
  };
  return queue;
}
createUpdateQueue - 创建 UpdateQueue
function appendUpdateToQueue<State>(
  queue: UpdateQueue<State>,
  update: Update<State>,
) {
  // 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;
  }
}
appendUpdateToQueue - 将 Update 加到队列

暂时将 scheduleWork 跳过

小结

 

 

  

  1.  ReactFiberScheduler.js 定义了
    • 全局变量 
    1. nextFlushedRoot 下一次渲染root
    2. nextFlushedExpirationTime 下一次渲染过期时间
    3. currentSchedulerTime 当前计划时间
    4. currentRendererTime 当前渲染时间
  2. FiberRoot 下的 context 属性 被赋值 上下文空对象
  3. FiberRoot 下的 current: FiberNode 对象 几个属性都被赋值
posted @ 2021-05-11 16:48  远方的少年🐬  阅读(86)  评论(0编辑  收藏  举报