咱们先来看一个简略的 demo:

import * as React from 'react';import * as ReactDOM from 'react-dom';class App extends React.Component {    render() {        return (            <div className="container">                <div className="section">                    <h1>This is the title.</h1>                    <p>This is the first paragraph.</p>                    <p>This is the second paragraph.</p>                </div>            </div>        );    }}ReactDOM.render(<App />, document.getElementById('root'));

首次渲染的调用栈如下图

以 performSyncWorkOnRoot 和 commitRoot 两个办法为界线,能够把 ReactDOM.render 分为三个阶段:

  1. Init
  2. Render
  3. Commit

    Init Phase

    render

    很简略,间接调用 legacyRenderSubtreeIntoContainer。

    export function render(  element: React$Element<any>,  container: Container,  callback: ?Function,) {  // 省略对 container 的校验逻辑  return legacyRenderSubtreeIntoContainer( null, element, container, false, callback,  );}

    这里须要留神一点,此时的 element 曾经不是 render 中传入的 <App /> 了,而是通过 React.createElement 转换后的一个 ReactElement 对象。

    legacyRenderSubtreeIntoContainer

    在这里咱们能够看到办法取名的重要性,一个好的办法名能够让你一眼就看出这个办法的作用。legacyRenderSubtreeIntoContainer,顾名思义,这是一个遗留的办法,作用是渲染子树并将其挂载到 container 上。再来看一下入参,children 和 container 别离是之前传入 render 办法的 App 元素和 id 为 root 的 DOM 元素,所以能够看出这个办法会依据 App 元素生成对应的 DOM 树,并将其挂在到 root 元素上。

    function legacyRenderSubtreeIntoContainer(  parentComponent: ?React$Component<any, any>,  children: ReactNodeList,  container: Container,  forceHydrate: boolean,  callback: ?Function,) {  let root: RootType = (container._reactRootContainer: any);  let fiberRoot;  if (!root) { root = container._reactRootContainer = legacyCreateRootFromDOMContainer(   container,   forceHydrate, ); fiberRoot = root._internalRoot; // 省略对 callback 的解决逻辑 unbatchedUpdates(() => {   updateContainer(children, fiberRoot, parentComponent, callback); });  } else { // 省略 else 逻辑  }  return getPublicRootInstance(fiberRoot);}

    上面来细看一下这个办法:

  4. 首次挂载时,会通过 legacyCreateRootFromDOMContainer 办法创立 container._reactRootContainer 对象并赋值给 root。 container 对象当初长这样:_

  1. 初始化 fiberRoot 为 root._internalRoot,类型为 FiberRootNode。fiberRoot 有一个极其重要的 current 属性,类型为 FiberNode,而 FiberNode 为 Fiber 节点的对应的类型。所以说 current 对象是一个 Fiber 节点,不仅如此,它还是咱们要结构的 Fiber 树的头节点,咱们称它为 rootFiber。到目前为止,咱们能够失去下图的指向关系:_

  1. 将 fiberRoot 以及其它参数传入 updateContainer 造成回调函数,将回调函数传入 unbatchedUpdates 并调用。

    unbatchedUpdates

    次要逻辑就是调用回调函数 fn,也就是之前传入的 updateContainer。

    export function unbatchedUpdates<A, R>(fn: (a: A) => R, a: A): R {  const prevExecutionContext = executionContext;  executionContext &= ~BatchedContext;  executionContext |= LegacyUnbatchedContext;  try { // fn 为之前传入的 updateContainer return fn(a);  } finally { executionContext = prevExecutionContext; if (executionContext === NoContext) {   resetRenderTimer();   flushSyncCallbackQueue(); }  }}

    updateContainer

    updateContainer 办法做的还是一些杂活,咱们简略总结一下:

  2. 计算以后 Fiber 节点的 lane(优先级)。
  3. 依据 lane(优先级),创立以后 Fiber 节点的 update 对象,并将其入队。
  4. 调度以后 Fiber 节点(rootFiber)。

    export function updateContainer(  element: ReactNodeList,  container: OpaqueRoot,  parentComponent: ?React$Component<any, any>,  callback: ?Function,): Lane {  const current = container.current;  const eventTime = requestEventTime();  // 计算以后节点的 lane(优先级)  const lane = requestUpdateLane(current);  if (enableSchedulingProfiler) { markRenderScheduled(lane);  }  const context = getContextForSubtree(parentComponent);  if (container.context === null) { container.context = context;  } else { container.pendingContext = context;  }  // 依据 lane(优先级)计算以后节点的 update 对象  const update = createUpdate(eventTime, lane);  update.payload = {element};  callback = callback === undefined ? null : callback;  if (callback !== null) { update.callback = callback;  }  // 将 update 对象入队  enqueueUpdate(current, update);  // 调度以后 Fiber节点(rootFiber)  scheduleUpdateOnFiber(current, lane, eventTime);  return lane;}

    scheduleUpdateOnFiber

    接着会进入 scheduleUpdateOnFiber 办法,依据 lane(优先级)等于 SyncLane,代码最终会执行 performSyncWorkOnRoot 办法。performSyncWorkOnRoot 翻译过去,就是指执行根节点(rootFiber)的同步工作,所以 ReactDOM.render 的首次渲染其实是一个同步的过程。

到这里大家可能会有个疑难,为什么 ReactDOM.render 触发的首次渲染是一个同步的过程呢?不是说在新的 Fiber 架构下,render 阶段是一个可打断的异步过程。
咱们先来看看 lane 是怎么计算失去的,相干逻辑在 updateContainer 中的 requestUpdateLane 办法里:

export function requestUpdateLane(fiber: Fiber): Lane {  const mode = fiber.mode;  if ((mode & BlockingMode) === NoMode) {    return (SyncLane: Lane);  } else if ((mode & ConcurrentMode) === NoMode) {    return getCurrentPriorityLevel() === ImmediateSchedulerPriority      ? (SyncLane: Lane)      : (SyncBatchedLane: Lane);  } else if (    !deferRenderPhaseUpdateToNextBatch &&    (executionContext & RenderContext) !== NoContext &&    workInProgressRootRenderLanes !== NoLanes  ) {   return pickArbitraryLane(workInProgressRootRenderLanes);  }  // 省略非核心代码}

能够看出 lane 的计算是由以后 Fiber 节点(rootFiber)的 mode 属性决定的,这里的 mode 属性其实指的就是以后 Fiber 节点的渲染模式,而 rootFiber 的 mode 属性其实最终是由 React 的启动形式决定的。
React 其实有三种启动模式:

  • Legacy Mode: ReactDOM.render(<App />, rootNode)。这是目前 React App 应用的形式,以后没有删除这个模式的打算,然而这个模式不反对一些新的性能。
  • Blocking Mode:ReactDOM.createBlockingRoot(rootNode).render(<App />)。目前正在试验中,作为迁徙到 concurrent 模式的第一个步骤。
  • Concurrent Mode: ReactDOM.createRoot(rootNode).render(<App />)。目前正在试验中,在将来稳固之后,将作为 React 的默认启动形式。此模式启用所有新性能。

因而不同的渲染模式在挂载阶段的差别,实质上来说并不是工作流的差别(其工作流波及 初始化 → render → commit 这 3 个步骤),而是 mode 属性的差别。mode 属性决定着这个工作流是零打碎敲(同步)的,还是分片执行(异步)的。

Render Phase

performSyncWorkOnRoot

外围是调用 renderRootSync 办法

renderRootSync

有两个外围办法 prepareFreshStack 和 workLoopSync,上面来一一剖析。

prepareFreshStack

首先调用 prepareFreshStack 办法,prepareFreshStack 中有一个重要的办法 createWorkInProgress。

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {  let workInProgress = current.alternate;  if (workInProgress === null) {    // 通过 current 创立 workInProgress    workInProgress = createFiber(      current.tag,      pendingProps,      current.key,      current.mode,    );    workInProgress.elementType = current.elementType;    workInProgress.type = current.type;    workInProgress.stateNode = current.stateNode;    // 使 workInProgress 与 current 通过 alternate 互相指向    workInProgress.alternate = current;    current.alternate = workInProgress;  } else {    // 省略 else 逻辑  }  // 省略对 workInProgress 属性的解决逻辑  return workInProgress;}

上面咱们来看一下 workInProgress 到底是什么?workInProgress 是 createFiber 的返回值,接着来看一下 createFiber。

const createFiber = function(  tag: WorkTag,  pendingProps: mixed,  key: null | string,  mode: TypeOfMode,): Fiber {  return new FiberNode(tag, pendingProps, key, mode);};

能够看出 createFiber 其实就是在创立一个 Fiber 节点。所以说 workInProgress 其实就是一个 Fiber 节点。
从 createWorkInProgress 中,咱们还能够看出:

  1. workInProgress 节点是 current 节点(rootFiber)的一个正本。
  2. workInProgress 节点与 current 节点(rootFiber)通过 alternate 属性互相指向。

所以到当初为止,咱们的 Fiber 树如下:

workLoopSync

接下来调用 workLoopSync 办法,代码很简略,若 workInProgress 不为空,调用 performUnitOfWork 解决 workInProgress 节点。

function workLoopSync() {  while (workInProgress !== null) {    performUnitOfWork(workInProgress);  }}

performUnitOfWork

performUnitOfWork 有两个重要的办法 beginWork 和 completeUnitOfWork,在 Fiber 的构建过程中,咱们只需重点关注 beginWork 这个办法。

function performUnitOfWork(unitOfWork: Fiber): void {  const current = unitOfWork.alternate;  setCurrentDebugFiberInDEV(unitOfWork);  let next;  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {    startProfilerTimer(unitOfWork);    next = beginWork(current, unitOfWork, subtreeRenderLanes);    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);  } else {    next = beginWork(current, unitOfWork, subtreeRenderLanes);  }  resetCurrentDebugFiberInDEV();  unitOfWork.memoizedProps = unitOfWork.pendingProps;  if (next === null) {    completeUnitOfWork(unitOfWork);  } else {    workInProgress = next;  }  ReactCurrentOwner.current = null;}

目前咱们只能看出,它会对以后的 workInProgress 节点进行解决,至于怎么解决的,当咱们解析完 beginWork 办法再来总结 performUnitOfWork 的作用。

beginWork

依据 workInProgress 节点的 tag 进行逻辑散发。tag 属性代表的是以后 Fiber 节点的类型,常见的有上面几种:

  • FunctionComponent:函数组件(包含 Hooks)
  • ClassComponent:类组件
  • HostRoot:Fiber 树根节点
  • HostComponent:DOM 元素
  • HostText:文本节点

    function beginWork(current: Fiber | null,workInProgress: Fiber,renderLanes: Lanes,): Fiber | null {// 省略非核心(针对树构建)逻辑switch (workInProgress.tag) {  // 省略局部 case 逻辑  // 函数组件(包含 Hooks)  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,      renderLanes,    );  }  // 类组件  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,      renderLanes,    );  }  // 根节点  case HostRoot:    return updateHostRoot(current, workInProgress, renderLanes);  // DOM 元素  case HostComponent:    return updateHostComponent(current, workInProgress, renderLanes);  // 文本节点  case HostText:    return updateHostText(current, workInProgress);  // 省略局部 case 逻辑}// 省略匹配不上的错误处理}

    以后的 workInProgress 节点为 rootFiber,tag 对应为 HostRoot,会调用 updateHostRoot 办法。

rootFiber 的 tag(HostRoot)是什么来的?外围代码如下:

export function createHostRootFiber(tag: RootTag): Fiber {  // 省略非核心代码  return createFiber(HostRoot, null, null, mode);}

在创立 rootFiber 节点的时候,间接指定了 tag 参数为 HostRoot。

updateHostRoot

updateHostRoot 的次要逻辑如下:

  1. 调用 reconcileChildren 办法创立 workInProgress.child。
  2. 返回 workInProgress.child。

    function updateHostRoot(current, workInProgress, renderLanes) { // 省略非核心逻辑  if (root.hydrate && enterHydrationState(workInProgress)) {   // 省略 if 成立的逻辑  } else { reconcileChildren(current, workInProgress, nextChildren, renderLanes); resetHydrationState();  }  return workInProgress.child;}

    这里有一点须要留神,通过查看源码,你会发现不仅是 updateHostRoot 办法,所以的更新办法最终都会调用上面这个办法:

    reconcileChildren(current, workInProgress, nextChildren, renderLanes);

    只是针对不同的节点类型,会有一些不同的解决,最终必由之路。

    reconcileChildren

    reconcileChildren 依据 current 是否为空进行逻辑散发。

    export function reconcileChildren(  current: Fiber | null,  workInProgress: Fiber,  nextChildren: any,  renderLanes: Lanes,) {  if (current === null) {workInProgress.child = mountChildFibers(   workInProgress,   null,   nextChildren,   renderLanes, );  } else { workInProgress.child = reconcileChildFibers(   workInProgress,   current.child,   nextChildren,   renderLanes, );  }}

    此时 current 节点不为空,会走 else 逻辑,调用 reconcileChildFibers 创立 workInProgress.child 对象。

    reconcileChildFibers

    依据 newChild 的类型进行不同的逻辑解决。

    function reconcileChildFibers( returnFiber: Fiber, currentFirstChild: Fiber | null, newChild: any, lanes: Lanes,  ): Fiber | null { // 省略非核心代码 const isObject = typeof newChild === 'object' && newChild !== null; if (isObject) {   switch (newChild.$$typeof) {     case REACT_ELEMENT_TYPE:       return placeSingleChild(         reconcileSingleElement(           returnFiber,           currentFirstChild,           newChild,           lanes,         ),       );   // 省略其余 case 逻辑  } } // 省略非核心代码 if (isArray(newChild)) {   return reconcileChildrenArray(     returnFiber,     currentFirstChild,     newChild,     lanes,   ); } // 省略非核心代码  }

    newChild 很要害,咱们先明确一下 newChild 到底是什么?通过层层向上寻找,你会在 updateHostRoot 办法中发现它其实是最开始传入 render 办法的 App 元素,它在 updateHostRoot 中被叫做 nextChildren,到这里咱们能够做出这样的猜测,rootFiber 的下一个是 App 节点,并且 App 节点是由 App 元素生成的,上面来看一下 newChild 的构造:

能够看出 newChild 类型为 object,$$typeof 属性为 REACT_ELEMENT_TYPE,所以会调用:

placeSingleChild(  reconcileSingleElement(    returnFiber,    currentFirstChild,    newChild,    lanes,  ),);
reconcileSingleElement

上面持续看 reconcileSingleElement 这个办法:

function reconcileSingleElement(  returnFiber: Fiber,  currentFirstChild: Fiber | null,  element: ReactElement,  lanes: Lanes,): Fiber {  const key = element.key;  let child = currentFirstChild;    // 省略 child 不存在的解决逻辑  if (element.type === REACT_FRAGMENT_TYPE) {    // 省略 if 成立的解决逻辑  } else {    const created = createFiberFromElement(element, returnFiber.mode, lanes);    created.ref = coerceRef(returnFiber, currentFirstChild, element);    created.return = returnFiber;    return created;  }}

办法的调用比拟深,咱们先明确一下入参,returnFiber 为 workInProgress 节点,element 其实就是传入的 newChild,也就是 App 元素,所以这个办法的作用为:

  1. 调用 createFiberFromElement 办法依据 App 元素创立 App 节点。
  2. 将新生成的 App 节点的 return 属性指向以后 workInProgress 节点(rootFiber)。此时 Fiber 树如下图:

  1. 返回 App 节点。

    placeSingleChild

    接下来调用 placeSingleChild:

    function placeSingleChild(newFiber: Fiber): Fiber {  if (shouldTrackSideEffects && newFiber.alternate === null) { newFiber.flags = Placement;  }  return newFiber;}

    入参为之前创立的 App 节点,它的作用为:

  2. 以后的 App 节点打上一个 Placement 的 flags,示意新增这个节点。
  3. 返回 App 节点。

之后 App 节点会被一路返回到的 reconcileChildren 办法:

workInProgress.child = reconcileChildFibers(  workInProgress,  current.child,  nextChildren,  renderLanes,);

此时 workInProgress 节点的 child 属性会指向 App 节点。此时 Fiber 树为:

beginWork 小结

beginWork 的链路比拟长,咱们来梳理一下:

  1. 依据 workInProgress.tag 进行逻辑散发,调用形如 updateHostRoot、updateClassComponent 等更新办法。
  2. 所有的更新办法最终都会调用 reconcileChildren,reconcileChildren 依据 current 进行简略的逻辑散发。
  3. 之后会调用 mountChildFibers/reconcileChildFibers 办法,它们的作用是依据 ReactElement 对象生成 Fiber 节点,并打上相应的 flags,示意这个节点是新增,删除还是更新等等。
  4. 最终返回新创建的 Fiber 节点。

简略来说就是创立新的 Fiber 字节点,并将其挂载到 Fiber 树上,最初返回新创建的子节点。

performUnitOfWork 小结

上面咱们来小结一下 performUnitOfWork 这个办法,先来回顾一下 workLoopSync 办法。

function workLoopSync() {  while (workInProgress !== null) {    performUnitOfWork(workInProgress);  }}

它会循环执行 performUnitOfWork,而 performUnitOfWork,咱们曾经晓得它会通过 beginWork 创立新的 Fiber 节点。它还有另外一个作用,那就是把 workInProgress 更新为新创建的 Fiber 节点,相干逻辑如下:

// 省略非核心代码// beginWork 返回新创建的 Fiber 节点并赋值给 nextnext = beginWork(current, unitOfWork, subtreeRenderLanes);// 省略非核心代码if (next === null) {  completeUnitOfWork(unitOfWork);} else {  // 若 Fiber 节点不为空则将 workInProgress 更新为新创建的 Fiber 节点  workInProgress = next;}

所以当 performUnitOfWork 执行完,以后的 workInProgress 都存储着下次要解决的 Fiber 节点,为下一次的 workLoopSync 做筹备。
performUnitOfWork 作用总结如下:

  1. 通过调用 beginWork 创立新的 Fiber 节点,并将其挂载到 Fiber 树上
  2. 将 workInProgress 更新为新创建的 Fiber 节点。

    App 节点的解决

    rootFiber 节点解决实现之后,对应的 Fiber 树如下:

接下来 performUnitOfWork 会开始解决 App 节点。App 节点的处理过程大抵与 rootFiber 节点相似,就是调用 beginWork 创立新的子节点,也就是 className 为 container 的 div 节点,解决实现之后的 Fiber 树如下:

这里有一个很要害的中央须要大家留神。咱们先回顾一下对 rootFiber 的解决,针对 rootFiber,咱们曾经晓得在 updateHostRoot 中,它会提取出 nextChildren,也就是最后传入 render 办法的 element。
那针对 App 节点,它是如何获取 nextChildren 的呢?先来看下咱们的 App 组件:

class App extends React.Component {    render() {        return (            <div className="container">                <div className="section">                    <h1>This is the title.</h1>                    <p>This is the first paragraph.</p>                    <p>This is the second paragraph.</p>                </div>            </div>        );    }}

咱们的 App 是一个 class,React 首先会实例化会它:

之后会把生成的实例挂在到以后 workInProgress 节点,也就是 App 节点的 stateNode 属性上:

而后在 updateClassComponent 办法中,会先初始化 instance 为 workInProgress.stateNode,之后调用 instance 的 render 办法并赋值给 nextChildren:

此时的 nextChildren 为上面 JSX 通过 React.createElement 转化后的后果:

<div className="container">    <div className="section">        <h1>This is the title.</h1>        <p>This is the first paragraph.</p>        <p>This is the second paragraph.</p>    </div></div>

接着来看一下 nextChildren 长啥样:

props.children 存储的是其子节点,它能够是对象也能够是数组。对于 App 节点和第一个 div 节点,它们都只有一个子节点。对于第二个 div 节点,它有三个子节点,别离是 h1、p、p,所以它的 children 为数组。

并且 props 还会保留在新生成的 Fiber 节点的 pendingProps 属性上,相干逻辑如下:

export function createFiberFromElement(  element: ReactElement,  mode: TypeOfMode,  lanes: Lanes,): Fiber {  let owner = null;  const type = element.type;  const key = element.key;  const pendingProps = element.props;  const fiber = createFiberFromTypeAndProps(    type,    key,    pendingProps,    owner,    mode,    lanes,  );  return fiber;}export function createFiberFromTypeAndProps(  type: any, // React$ElementType  key: null | string,  pendingProps: any,  owner: null | Fiber,  mode: TypeOfMode,  lanes: Lanes,): Fiber {  // 省略非核心逻辑  const fiber = createFiber(fiberTag, pendingProps, key, mode);  fiber.elementType = type;  fiber.type = resolvedType;  fiber.lanes = lanes;  return fiber;}

第一个 div 节点的解决

App 节点的 nextChildren 是通过结构实例并调用 App 组件内的 render 办法失去的,那对于第一个 div 节点,它的 nextChildren 是如何获取的呢?
针对 div 节点,它的 tag 为 HostComponent,所以在 beginWork 中会调用 updateHostComponent 办法,能够看出 nextChildren 是从以后 workInProgress 节点的 pendingProps 上获取的。

function updateHostComponent(  current: Fiber | null,  workInProgress: Fiber,  renderLanes: Lanes,) {  // 省略非核心逻辑  const nextProps = workInProgress.pendingProps;  // 省略非核心逻辑  let nextChildren = nextProps.children;  // 省略非核心逻辑  reconcileChildren(current, workInProgress, nextChildren, renderLanes);  return workInProgress.child;}

咱们之前说过,在创立新的 Fiber 节点时,咱们会把下一个子节点元素保留在 pendingProps 中。当下次调用更新办法(形如 updateHostComponent )时,咱们就能够间接从 pendingProps 中获取下一个子元素。
之后的逻辑同上,解决完第一个 div 节点后的 Fiber 树如下图:

第二个 div 节点的解决

咱们先看一下第二个 div 节点:

<div className="section">  <h1>This is the title.</h1>  <p>This is the first paragraph.</p>  <p>This is the second paragraph.</p></div>

它比拟非凡,有三个字节点,对应的 nextChildren 为

上面咱们来看看 React 是如何解决多节点的状况,首先咱们还是会进入 reconcileChildFibers 这个办法:

function reconcileChildFibers(  returnFiber: Fiber,  currentFirstChild: Fiber | null,  newChild: any,  lanes: Lanes,): Fiber | null {    // 省略非核心代码  if (isArray(newChild)) {    return reconcileChildrenArray(      returnFiber,      currentFirstChild,      newChild,      lanes,    );  }    // 省略非核心代码}

newChild 即是 nextChildren,为数组,会调用 reconcileChildrenArray 这个办法

function reconcileChildrenArray(  returnFiber: Fiber,  currentFirstChild: Fiber | null,  newChildren: Array<*>,  lanes: Lanes,): Fiber | null {  // 省略非核心逻辑  let previousNewFiber: Fiber | null = null;  let oldFiber = currentFirstChild;  // 省略非核心逻辑  if (oldFiber === null) {    for (; newIdx < newChildren.length; newIdx++) {      const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);      if (newFiber === null) {        continue;      }      lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);      if (previousNewFiber === null) {        resultingFirstChild = newFiber;      } else {        previousNewFiber.sibling = newFiber;      }      previousNewFiber = newFiber;    }    return resultingFirstChild;  }  // 省略非核心逻辑}

上面来总结一下这个办法:

  1. 遍历所有的子元素,通过 createChild 办法依据子元素创立子节点,并将每个字元素的 return 属性指向父节点。
  2. 用 resultingFirstChild 来标识第一个子元素。
  3. 将子元素用 sibling 相连。

最初咱们的 Fiber 树就构建实现了,如下图: