react-reading-sources icon indicating copy to clipboard operation
react-reading-sources copied to clipboard

[Note] ReactDOM.render流程再梳理

Open ddzy opened this issue 5 years ago • 0 comments

前言

前几天已经简单总结过了ReactDOM.render的简单流程(#1).

但是没有进行深入的理解, 对于内部机制, 比如:

  • reconcile
  • schedule
  • ...

没有过多的去解读, 毕竟错综复杂的源码加上FB那帮人的神奇脑洞.

让整个react源码变得晦涩难懂.

所以决定新开一个issue, 对照源码, 将ReactDOM.render的流程再梳理一遍.

必备

react体系中有几个常见的对象

  • DOMContainer
  • ReactRoot
  • FiberRoot
  • RootFiber
  • Fiber

至于它们之间的关系, 我之前有画过简单的xmind脑图:

https://github.com/ddzy/react-reading-sources/blob/master/xmind/1-react%E5%90%84%E5%A4%A7%E8%8A%82%E7%82%B9%E4%B9%8B%E9%97%B4%E7%9A%84%E5%85%B3%E7%B3%BB.xmind

调和(reconcile)

节点准备

通过一系列的内部函数调用, 分别创建ReactRootFiberRootRootFiber等多个内部节点

  • ReactDOM.render

应用入口

源码
const ReactDOM = {
  render(
      element,
      container,
  ) {
      return legacyRenderSubtreeIntoContainer(
          null,
          element,
          container,
          false,
          callback,
      );
  }
};
  • legacyRenderSubtreeIntoContainer

创建react应用根节点 —— ReactRoot, 并且调用ReactRoot.render开始渲染

源码
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {
  let root: Root = (container._reactRootContainer: any);

  if (!root) {
    // 创建React应用根节点
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    // 首次渲染, 不采用批量更新
    unbatchedUpdates(() => {
      root.render(children, callback);
    });
  } 
}
  • ReactRoot

react根节点构造函数. 内部会创建FiberRoot

源码
function ReactRoot(
  // DOM根节点
  container: DOMContainer,
  // 是否启用ConcurrentMode, 也就是任务优先级、任务切片, 默认为false
  isConcurrent: boolean,
) {
  // 创建FiberRoot
  // 记录整个应用更新过程中的各种信息
  const root = createContainer(container, isConcurrent, hydrate);
  this._internalRoot = root;
}
  • createContainer

依次调用createFiberRootcreateHostRootFiber, 来创建并返回一个新的FiberRoot对象

源码
function createContainer(
  containerInfo: DOMContainer,
  isConcurrent: boolean = false,
) {
  return createFiberRoot(containerInfo, isConcurrent);
}
源码
function createFiberRoot(
  containerInfo: DOMContainer,
  isConcurrent: boolean = false,
) {
  // 创建RootFiber => HostRoot
  const uninitializedFiber = createHostRootFiber(isConcurrent);

  return {
      // 对应的DOMContainer
      containerInfo: any,

      // Used only by persistent updates.
      pendingChildren: any,

      // FiberRoot => 整个`Fiber树`的顶点 => HostRootFiber
      current: Fiber,

      // 暂停提交的最早和最新优先级
      earliestSuspendedTime: ExpirationTime,
      latestSuspendedTime: ExpirationTime,

      // 不确定是否暂停的最新和最老的优先级
      earliestPendingTime: ExpirationTime,
      latestPendingTime: ExpirationTime,

      // promise执行resolve之后的最新优先级
      latestPingedTime: ExpirationTime,

      pingCache:
        | WeakMap<Thenable, Set<ExpirationTime>>
        | Map<Thenable, Set<ExpirationTime>>
        | null,

      // 如果出现错误, 并且队列中没有更多的更新
      // 在处理错误之前, 会重新从根开始同步渲染
      didError: boolean,

      pendingCommitExpirationTime: ExpirationTime,
      // A finished work-in-progress HostRoot that's ready to be committed.

      // 完成调度&更新的RootFiber.alternate
      finishedWork: Fiber | null,

      // Timeout handle returned by setTimeout. Used to cancel a pending timeout, if
      // it's superseded by a new one.
      timeoutHandle: TimeoutHandle | NoTimeout,
      // Top context object, used by renderSubtreeIntoContainer
      context: Object | null,
      pendingContext: Object | null,
      // Determines if we should attempt to hydrate on the initial mount
      +hydrate: boolean,

      // 当前RootFiber剩余的更新时间
      nextExpirationTimeToWorkOn: ExpirationTime,

      // 更新过期时间
      expirationTime: ExpirationTime,

      // List of top-level batches. This list indicates whether a commit should be
      // deferred. Also contains completion callbacks.
      // TODO: Lift this into the renderer
      firstBatch: Batch | null,

      // FiberRoot为单向链表结构
      // 防止有多个root, 也就是调用多次ReactDOM.render
      nextScheduledRoot: FiberRoot | null,
  };
}

节点更新

创建好了基本的节点, 开始更新

又回到了legacyRenderSubtreeIntoContainer这个方法

其内部调用了ReactRoot.render(children)来进行首次更新.

  • ReactRoot.prototype.render
源码
ReactRoot.prototype.render = function(
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  const root = this._internalRoot;

  // 与`createContainer`相比
  // 前者创建`FiberRoot`
  // 后者更新`FiberRoot`
  updateContainer(children, root);
};
  • updateContainer

更新RootFiber

源码
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
){

  // 获取RootFiber => HostRoot
  const current = container.current;

  // 是`动态`的, 表示页面从js开始加载到当前的时间戳, 也有可能是上一个fiber的`currentSchedulerTime`, 这 
是为了让批量更新具有相同的expirationTime, 避免多次更新.
  const currentTime = requestCurrentTime();

  // `concurrent`根据其来进行优先级更新调度, 值越小, 优先级越高
  const expirationTime = computeExpirationForFiber(currentTime, current);

  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}

由于requestCurrentTimecomputeExpirationTimeForFiber非常重要, 所以还是mark一下

先来看一下requestCurrentTime, 它的作用是: 计算从页面开始加载到当前的时间戳

也有可能是另外几种情况, 会在下面的源码中进行注释:

源码
function requestCurrentTime() {
  if (isRendering) {
    // 已经开始渲染了, 为了避免重复更新, 所以返回上一次的调度时间
    // currentRendererTime >= currentSchedulerTime
    return currentSchedulerTime;
  }
  
  findHighestPriorityRoot();
  if (
    nextFlushedExpirationTime === NoWork ||
    nextFlushedExpirationTime === Never
  ) {
    // 如果没有等待处理的任务
    // 更新渲染时间, 也就是`currentRendererTime`
    recomputeCurrentRendererTime();
    currentSchedulerTime = currentRendererTime;

    return currentSchedulerTime;
  }

  // 如果有被挂起的, 也就是等待处理的任务
  // 直接返回该任务的currentSchedulerTime
  return currentSchedulerTime;
}

看到这里, requestCurrentTime利用currentSchedulerTime这个变量

巧妙地让批量更新(batchedUpdates)具有相同的expirationTime, 避免了expirationTime的不同导致进行多次更新, 影响性能

话不多说, 接着来看computeExpirationTimeForFiber这个方法, 其内部对5种不同类型的expirationTime进行了不同的处理:

源码
function computeExpirationForFiber(
  // 经由`requestCurrentTime`计算出来的值
  currentTime: ExpirationTime, 
  // fiber对象
  fiber: Fiber
) {
  let expirationTime;

  if (expirationContext !== NoWork) {

  } else if (isWorking) {
    if (isCommitting) {
      // 在commit提交阶段产生的更新, 应该是同步(Sync)的expirationTime
      // ①: Sync
      expirationTime = Sync;
    } else {
      // render渲染阶段产生的更新, 应该与上一次具有相同的expirationTime
      expirationTime = nextRenderExpirationTime;
    }
  } else {
    // 如果没有正在进行的工作, 也就是不处于`isWorking`状态

    // 根据`isConcurrentMode`来判断是否开启异步渲染模式
    if (fiber.mode & ConcurrentMode) {
      if (isBatchingInteractiveUpdates) {
        // 如果是交互式(Interactive)更新, react的事件系统
        // ②: Interactive
        expirationTime = computeInteractiveExpiration(currentTime);
      } else {
        // 如果是普通的异步(Sync)更新, 普通的setState
        // ③: Async
        expirationTime = computeAsyncExpiration(currentTime);
      }
    } else {
      // 同步渲染
      // 因为在`legacyCreateRootFromDOMContainer`函数内部, 默认设置了`isConcurrent`为false
      // ④: NoWork
      expirationTime = Sync;
    }
  }

  return expirationTime;
}
  • updateContainerAtExpirationTime

  • scheduleRootUpdate

reconcile -> schedule的桥梁

在其内部进行创建更新(update), 更新入队...等一系列操作

之后进入schedule阶段, 嘀嘀嘀

源码
function scheduleRootUpdate(
  // RootFiber
  current: Fiber,
  // ReactDOM.render的第一个参数
  element: ReactNodeList,
  expirationTime: ExpirationTime,
) {
  // 创建更新
  const update = createUpdate(expirationTime);
  
  // 更新参数
  update.payload = {element};
  update.callback = callback;

  // 更新入队
  // 将更新追加到对应fiber的`updateQueue`上
  enqueueUpdate(current, update);

  // 进入调度(schedule)阶段
  scheduleWork(current, expirationTime);
}

--------------------------------------简简单单的分割线--------------------------------------------------

调度(schedule)

经过了上面的调和(reconcile)阶段, 已经做好了基本的准备工作:

  • 计算expirationTime
  • update的创建、初始化、入队
  • 其它的杂七杂八的操作

而在调和阶段的最后, 有一段这样的代码:

源码
scheduleWork(current, expirationTime);

标志着, 整个应用开始进入最核心的调度(schedule)阶段

scheduleWork

调度主入口. ReactDOM.renderClassComponentInstance.setState... 都会进行调用该方法:

源码
function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  // 寻找`FiberRoot`, 更新`expirationTime`
  const root = scheduleWorkToRoot(fiber, expirationTime);

  // ! [MARK]: nextRenderExpirationTime -> 当前正在调度工作的过期时间
  // ! [MARK]: expirationTime -> 新产生的任务的过期时间
  // ! [MARK]: childExpirationTime -> 快速确定子fiber有没有挂起的等待执行的任务

  // 此次判断就是fiber的特性之一 --> 任务优先级机制
  if (
    !isWorking &&
    nextRenderExpirationTime !== NoWork &&
    expirationTime > nextRenderExpirationTime
  ) {
    // 新的任务(ReactDOM.render)优先级较高, 终止当前任务(首次渲染时产生的更新)
    // nextUnitOfWork - 表示当前正在调度的fiber
    // 重置当前调度的任务为null
    resetStack();
  }

  // 标志优先级
  markPendingPriorityLevel(root, expirationTime);

  if (
    // If we're in the render phase, we don't need to schedule this root
    // for an update, because we'll do it before we exit...
    !isWorking ||
    isCommitting ||
    // ...unless this is a different root than the one we're rendering.
    nextRoot !== root
  ) {
    const rootExpirationTime = root.expirationTime;
    // 开始进入请求工作
    // react应用产生的所有更新, 都交由FiberRoot来处理.
    requestWork(root, rootExpirationTime);
  }
}

requestWork

请求工作:

源码
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  // 添加FiberRoot到调度队列
  addRootToSchedule(root, expirationTime);

  if (isRendering) {
    // Prevent reentrancy. Remaining work will be scheduled at the end of
    // the currently rendering batch.
    return;
  }

  /**
   * ! 注意点:
   * !    1). 批次更新
   * !    2). react的事件系统会自动设置`isBatchingUpdates`的值为true
   */
  if (isBatchingUpdates) {
    // 在当前批次渲染的最后, 直接进行同步更新
    if (isUnbatchingUpdates) {
      // ...unless we're inside unbatchedUpdates, in which case we should
      // flush it now.
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      performWorkOnRoot(root, Sync, false);
    }
    return;
  }

  /**
   * ! 注意点:
   * !    1). 同步任务直接执行, 不能被打断
   * !    2). 异步任务, requestIdleCallback
   */
  if (expirationTime === Sync) {
    // 执行同步任务
    performSyncWork();
  } else {
    // 在浏览器空闲时间, 执行异步任务
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

----------------------------------有空再更-----------------------------------

接着更新, 上面在requestWork中, 根据expirationTime是否同步or异步, 来进行不同的performWork, performWork内部机制是什么? 继续看源码.

performSyncWork

关于performSyncWork较简单, 由于属于同步任务, 所以直接省去requestIdleCallback这个环节, 直接调用performWork:

源码
function performSyncWork() {
  performWork(Sync);
}

scheduleCallbackWithExpirationTime

scheduleCallbackWithExpirationTime主要的功能是:

  • 计算timeout, 也就是requestIdleCallbacktimeout`参数

先来列出几个重要的全局变量:

  • callbackExpirationTime 正在分片的任务的expirationTime
  • callbackID 正在分片的任务的id, 功能类似于setTimeoutId, 但是结构不同, react自行定义了它的结构.

接着看一下源码:

源码
function scheduleCallbackWithExpirationTime(
  root: FiberRoot,
  expirationTime: ExpirationTime,
) {
  // 如果当前已经有任务在分片了 - 任务优先级机制
  if (callbackExpirationTime !== NoWork) {
    // 如果新的任务优先级 < 当前正在进行的任务
    if (expirationTime < callbackExpirationTime) {
      return;
    } else {
    // 如果新的任务优先级 > 当前正在进行的任务
      if (callbackID !== null) {
        // 强制退出当前分片的任务
        cancelCallback(callbackID);
      }
    }
  }

  /**
   * ! 注意点:
   * !    1). 浏览器正常的刷新频率为30HZ
   * !    2). 等同于在`一秒`内需要刷新30次
   * !    3). 每一帧的时间为`33ms`
   * !    4). 在`33ms`内需要做js执行、动画、重绘重排等操作
   */

  // 保存即将进行分片的任务
  callbackExpirationTime = expirationTime;
  const currentMs = now() - originalStartTimeMs;
  const expirationTimeMs = expirationTimeToMs(expirationTime);

  // timeout = 组件过期的时间 - 当前时间 = requestIdleCallback的timeout参数
  const timeout = expirationTimeMs - currentMs;

  // 模拟`requestIdleCallback`API
  // react通过`scheduleCallback`来实现任务分片机制
  // 在一个个任务分片中, 实现任务优先级
  // 将传递的`performAsyncWork`参数加入到双向循环链表.
  callbackID = scheduleCallback(performAsyncWork, {timeout});
}

performAsyncWork

react通过模拟requestIdleCallbackAPI, 使得浏览器可以在每一帧的空闲时间来执行react代码. react维护了一个callback双向循环链表. 因此, 浏览器可以在空闲时间遍历改链表, 进而实现任务的分片机制.

关于scheduleCallback是如何实现的? 或者react是如何模拟requestIdleCallback的? 后面再开新的issue记录吧.

源码
function performAsyncWork(didTimeout) {
  // 如果FiberRoot的expirationTime超时
  if (didTimeout) {
    if (firstScheduledRoot !== null) {
      // 更新FiberRoot单向循环链表, 更新所有已经超时的expirationTime
      // 为了安排它们在一个批次进行批量更新
      recomputeCurrentRendererTime();
      let root: FiberRoot = firstScheduledRoot;
      do {
        didExpireAtExpirationTime(root, currentRendererTime);
        // The root schedule is circular, so this is never null.
        root = (root.nextScheduledRoot: any);
      } while (root !== firstScheduledRoot);
    }
  }

  findHighestPriorityRoot();

  if (disableYielding) {
    while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork) {
      performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime, false);
      findHighestPriorityRoot();
    }
  } 

  // If we're inside a callback, set this to false since we just completed it.
  callbackExpirationTime = NoWork;
  callbackID = null;

  // 如果在本次时间分片内没有执行完任务
  // 重新安排
  if (nextFlushedExpirationTime !== NoWork) {
    scheduleCallbackWithExpirationTime(
      ((nextFlushedRoot: any): FiberRoot),
      nextFlushedExpirationTime,
    );
  }

  // Clean-up.
  finishRendering();
}

performWorkOnRoot

先梳理一下同/异步任务的走向:

源码
// 同步任务
performSyncWork  ->  performWorkSync  ->  performWork  ->  performWorkOnRoot

// 异步任务
scheduleCallbackWithExpirationTime  ->  scheduleCallback  ->  performAsyncWork  -> performWorkOnRoot

接着简单看一下performOnRoot, 此时进入isRendering阶段. 它包括两个部分:

  • 如果root.finishWork为null, 则进入isCommitting阶段(completeRoot)
  • 反之, 进入isWorking阶段(renderRoot)
源码
function performWorkOnRoot(
  root: FiberRoot,
  expirationTime: ExpirationTime,
  isYieldy: boolean,
) {
  isRendering = true;

  if (!isYieldy) {
    // ! 同步任务
    // ! yield --> 等待, 表示不能暂停、被打断的任务

    let finishedWork = root.finishedWork;
    if (finishedWork !== null) {
      completeRoot(root, finishedWork, expirationTime);
    } else {
      renderRoot(root, isYieldy);
      finishedWork = root.finishedWork;
      if (finishedWork !== null) {
        // We've completed the root. Commit it.
        completeRoot(root, finishedWork, expirationTime);
      }
    }
  } else {
    // ! 异步任务

    let finishedWork = root.finishedWork;
    if (finishedWork !== null) {
      // This root is already complete. We can commit it.
      completeRoot(root, finishedWork, expirationTime);
    } else {
      renderRoot(root, isYieldy);
      completeRoot(root, finishedWork, expirationTime);
    }
  }

  isRendering = false;
}

renderRoot

开始真正的渲染工作

源码
function renderRoot(root: FiberRoot, isYieldy: boolean): void {
  isWorking = true;
  const previousDispatcher = ReactCurrentDispatcher.current;
  ReactCurrentDispatcher.current = ContextOnlyDispatcher;

  const expirationTime = root.nextExpirationTimeToWorkOn;

  // ! 凡是带有`next`字样的, 都代表当前正在进行的工作, 反之则代表新的工作
  if (
    expirationTime !== nextRenderExpirationTime ||
    root !== nextRoot ||
    nextUnitOfWork === null
  ) {
    // 创建WorkInProgress-Fiber-Tree
    nextUnitOfWork = createWorkInProgress(
      nextRoot.current,
      null,
      nextRenderExpirationTime,
    );
    root.pendingCommitExpirationTime = NoWork;
  }

  // 是否出现致命错误
  let didFatal = false;

  do {
    try {
      // 大循环, 遍历整颗WorkInProgress树
      workLoop(isYieldy);
    } catch (thrownValue) {
      // 如果出现错误, 对应的错误处理机制
      if (nextUnitOfWork === null) {
        // This is a fatal error.
        didFatal = true;
        onUncaughtError(thrownValue);
      } else {
        const sourceFiber: Fiber = nextUnitOfWork;
        let returnFiber = sourceFiber.return;
        if (returnFiber === null) {
          // This is the root. The root could capture its own errors. However,
          // we don't know if it errors before or after we pushed the host
          // context. This information is needed to avoid a stack mismatch.
          // Because we're not sure, treat this as a fatal error. We could track
          // which phase it fails in, but doesn't seem worth it. At least
          // for now.
          didFatal = true;
          onUncaughtError(thrownValue);
        } else {
          throwException(
            root,
            returnFiber,
            sourceFiber,
            thrownValue,
            nextRenderExpirationTime,
          );
          nextUnitOfWork = completeUnitOfWork(sourceFiber);
          continue;
        }
      }
    }
    break;
  } while (true);

  // Yield back to main thread.
  // 如果渲染出现致命错误, 会退出并重新安排渲染
  if (didFatal) {
    onFatal(root);
    return;
  }

  // We completed the whole tree.
  const rootWorkInProgress = root.current.alternate;

  // Ready to commit.
  // 进入commit阶段
  onComplete(root, rootWorkInProgress, expirationTime);
}

渲染(render)

此时, 从RootFiberWorkInProgress的开始, 逐步遍历整个Fiber树, 进入渲染阶段

WorkLoop

源码
function workLoop(isYieldy) {
  if (!isYieldy) {
    // 同步任务
    // 不能被打断
    // nextUnitOfWork === currentFiber.child, 也就是下一次进行渲染的fiber
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    // 异步任务
    // 可以被优先级更高的打断
    while (nextUnitOfWork !== null && !shouldYield()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

performUnitOfWork

对单个Fiber节点进行更新. 同时更新fiber.stateNode, 也就是ReactElement.

源码
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
  const current = workInProgress.alternate;

  // next === fiber.return
  // 下一个渲染的fiber节点
  let next;

  next = beginWork(current, workInProgress, nextRenderExpirationTime);
  workInProgress.memoizedProps = workInProgress.pendingProps;

  ReactCurrentOwner.current = null;

  return next;
}

beginWork

开始真正的渲染, 此时会从根据当前fibertag属性, 来对不同的组件做对应的处理. 关于fiber.tag有哪些具体属性, 可以参考这篇笔记:

fiber.tag类型汇总

接着看一下源码:

源码
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;

  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (oldProps !== newProps || hasLegacyContextChanged()) {
      // If props or context changed, mark the fiber as having performed work.
      // This may be unset if the props are determined to be equal later (memo).

      // ! 根据`oldProps === newProps`判断是否已更新
      didReceiveUpdate = true;
    } else if (updateExpirationTime < renderExpirationTime) {
      // ! 如果当前fiber节点的子树产生更新的优先级小于当前更新

      didReceiveUpdate = false;
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      switch (workInProgress.tag) {
        case HostRoot:
          pushHostRootContext(workInProgress);
          resetHydrationState();
          break;

        // ! HostComponent -> 代表原生DOM元素的字符串(div、p)
        case HostComponent:
          pushHostContext(workInProgress);
          break;
        case ClassComponent: {
          const Component = workInProgress.type;
          if (isLegacyContextProvider(Component)) {
            pushLegacyContextProvider(workInProgress);
          }
          break;
        }
        case HostPortal:
          pushHostContainer(
            workInProgress,
            workInProgress.stateNode.containerInfo,
          );
          break;
        case ContextProvider: {
          const newValue = workInProgress.memoizedProps.value;
          pushProvider(workInProgress, newValue);
          break;
        }
        case Profiler:
          if (enableProfilerTimer) {
            workInProgress.effectTag |= Update;
          }
          break;
        case SuspenseComponent: {
          const state: SuspenseState | null = workInProgress.memoizedState;
          const didTimeout = state !== null;
          if (didTimeout) {
            // If this boundary is currently timed out, we need to decide
            // whether to retry the primary children, or to skip over it and
            // go straight to the fallback. Check the priority of the primary
            // child fragment.
            const primaryChildFragment: Fiber = (workInProgress.child: any);
            const primaryChildExpirationTime =
              primaryChildFragment.childExpirationTime;
            if (
              primaryChildExpirationTime !== NoWork &&
              primaryChildExpirationTime >= renderExpirationTime
            ) {
              // The primary children have pending work. Use the normal path
              // to attempt to render the primary children again.
              return updateSuspenseComponent(
                current,
                workInProgress,
                renderExpirationTime,
              );
            } else {
              // The primary children do not have pending work with sufficient
              // priority. Bailout.
              const child = bailoutOnAlreadyFinishedWork(
                current,
                workInProgress,
                renderExpirationTime,
              );
              if (child !== null) {
                // The fallback children have pending work. Skip over the
                // primary children and work on the fallback.
                return child.sibling;
              } else {
                return null;
              }
            }
          }
          break;
        }
        case DehydratedSuspenseComponent: {
          if (enableSuspenseServerRenderer) {
            // We know that this component will suspend again because if it has
            // been unsuspended it has committed as a regular Suspense component.
            // If it needs to be retried, it should have work scheduled on it.
            workInProgress.effectTag |= DidCapture;
            break;
          }
        }
      }
      return bailoutOnAlreadyFinishedWork(
        current,
        workInProgress,
        renderExpirationTime,
      );
    }
  } else {
    didReceiveUpdate = false;
  }

  // Before entering the begin phase, clear the expiration time.
  workInProgress.expirationTime = NoWork;

  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      const elementType = workInProgress.elementType;
      return mountIndeterminateComponent(
        current,
        workInProgress,
        elementType,
        renderExpirationTime,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case HostPortal:
      return updatePortalComponent(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ForwardRef: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === type
          ? unresolvedProps
          : resolveDefaultProps(type, unresolvedProps);
      return updateForwardRef(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderExpirationTime);
    case Mode:
      return updateMode(current, workInProgress, renderExpirationTime);
    case Profiler:
      return updateProfiler(current, workInProgress, renderExpirationTime);
    case ContextProvider:
      return updateContextProvider(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case ContextConsumer:
      return updateContextConsumer(
        current,
        workInProgress,
        renderExpirationTime,
      );
    case MemoComponent: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;

      // Resolve outer props first, then resolve inner props.
      let resolvedProps = resolveDefaultProps(type, unresolvedProps);
      resolvedProps = resolveDefaultProps(type.type, resolvedProps);

      return updateMemoComponent(
        current,
        workInProgress,
        type,
        resolvedProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case SimpleMemoComponent: {
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        workInProgress.type,
        workInProgress.pendingProps,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    case IncompleteClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return mountIncompleteClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderExpirationTime,
      );
    }
    case DehydratedSuspenseComponent: {
      if (enableSuspenseServerRenderer) {
        return updateDehydratedSuspenseComponent(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
      break;
    }
    case EventComponent: {
      if (enableEventAPI) {
        return updateEventComponent(
          current,
          workInProgress,
          renderExpirationTime,
        );
      }
      break;
    }
    case EventTarget: {
      if (enableEventAPI) {
        return updateEventTarget(current, workInProgress, renderExpirationTime);
      }
      break;
    }
  }
}

组件更新

根据fiber.tag, 不同的组件进行不同的更新. 那么, 具体是如何更新的? 避免篇幅过长, 将其拆分为多篇笔记:

提交(commit)

待看

总结

[2019-7-15]

看到了beginWork中对于FunctionComponent的更新, 但是hook机制较为复杂, 需要花较长时间理解.

简单总结下, react中处理一个更新, 大致会经过下列流程:

  • 产生更新
  • 计算expirationTime
  • 创建更新update
  • 更新入队updateQueue
  • 寻找FiberRoot
  • FiberRoot加入单向循环链表
  • 遍历FiberRoot链表, 通过findHighestPriorityRoot寻找最高优先级的FiberRoot, 依次执行performWorkOnRoot
  • isRendering
  • 找到RootFiber, 创建WorkInProgress
  • isWorking
  • 进入WorkLoop大循环, 对WorkInProgress返回的每个fiber.child做处理
  • 进入beginWork阶段, 根据当前fiber的tag做不同的更新.
  • ......

ddzy avatar Jul 04 '19 08:07 ddzy