React源码调试记录

565 阅读21分钟

搭建React源码本地调试环境

既然要阅读、调试源码,过程中肯定会出现一些不理解的地方,这时候就需要对源码进行调试,通过运行源码来查看结果与我们预计的结果是否一致,所以在阅读源码之前要先搭建一个源码调试环境。

这个环境并不是通过脚手架工具简单的创建一个React项目。因为脚手架内部使用的React源码与官方放在GitHub上的源码是有差别的,所以要直接克隆GitHub中的源码,再结合脚手架工具来搭建一个本地的源码调试环境。

相关步骤及命令如下:

// 1、使用create-react-app脚手架创建项目
npx create-react-app react-source

// 2、弹射create-react-app脚手架内部配置(运行此命令需要先初始化git 仓库)
npm run eject

// 3、克隆react官方源码(在项目的根目录进行克隆)
git clone --branch v16.13.1 --depth=1 https://github.com/facebook/react.git src/react

// 4、链接本地源码  将react-source/config/webpack.config.js中 resolve下的alias替换为:
alias: {
    "react-native": "react-native-web",
    "react": path.resolve(__dirname, "../src/react/packages/react"),
    "react-dom": path.resolve(__dirname, "../src/react/packages/react-dom"),
    "shared": path.resolve(__dirname, "../src/react/packages/shared"),
    "react-reconciler": path.resolve(__dirname, "../src/react/packages/react-reconciler"),
    "legacy-events": path.resolve(__dirname, "../src/react/packages/legacy-events")
}

// 5、修改环境变量
// 文件位置: react-source/config/env.js
// 用以下内容替换原文件的stringified
const stringified = {
    'process.env': Object.keys(raw).reduce((env, key) => {
        env[key] = JSON.stringify(raw[key]);
        return env;
    }, {}),
    __DEV__: true,
    SharedArrayBuffer: true,
    spyOnDev: true,
    spyOnDevAndProd: true,
    spyOnProd: true,
    __PROFILE__: true,
    __UMD__: true,
    __EXPERIMENTAL__: true,
    __VARIANT__: true,
    gate: true,
    trustedTypes: true
};

// 6、设置babel转换代码时忽略类型检
npm install @babel/plugin-transform-flow-strip-types -D
// 在webpack.config.js添加babel-loader的plugin
require.resolve("@babel/plugin-transform-flow-strip-types")

// 7、导出HostConfig
// 文件位置: ./react/packages/react-reconciler/src/ReactFiberHostConfig.js
+ export * from './forks/ReactFiberHostConfig.dom';
- invariant(false, 'This module must be shimmed by a specifix renderer')

//8、修改ReactSharedInternals.js文件
// 文件位置: /react/packages/shared/ReactSharedInternals.js
- import * as React from 'react';
- const ReactSharedInternals = React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
+ import ReactSharedInternals from '../react/src/ReactSharedInternals';

// 9、关闭 eslint 扩展
// 文件位置:react/.eslintrc.js [module.exports]
// 删除 extends
extends: [
  'fbjs',
  'prettier'
],

// 10、禁止 invariant 报错
// 文件位置: /react/packages/shared/invariant.js
// 替换为:
export default function invariant(condition, format, a, b, c, d, e, f) {
  if (condition) {
      return
  }
  throw new Error(
    'Internal React error: invariant() is meant to be replaced at compile ' +
      'time. There is no runtime version.',
  );
}

// 11、eslint 配置
// 在react源码文件夹中新建 .eslintrc.json并添加如下配置:
{
    "extends": "react-app",
    "globals": {
        "SharedArrayBuffer": true,
        "spyOnDev": true,
        "spyOnDevAndProd": true,
        "spyOnProd": true,
        "__PROFILE__": true,
        "__UMD__": true,
        "__EXPERIMENTAL__": true,
        "__VARIANT__": true,
        "gate": true,
        "trustedTypes": true
    }
}

// 12、修改react、react-dom引入方式 主要是src/index.js和src/App.js
import * as React from 'react'
import * as ReactDOM from 'react-dom'

// 13、解决vscode中flow报错
"javascript.validate.enable": false

// 14、可选配置(vscode 安装了prettier插件并在保存react源码文件时右下角出现错误)
// 解决方法:
npm i prettier -g
// vscode中
Settings > Extensions > Prettier > Prettier path 修改path路径即可

// 15、如果有__DEV__报错
删除node_modules文件夹,重新执行npm install

JSX转为ReactElement的过程

  1. 分离props属性和特殊属性。
    如果传入的config不为空,则先处理ref属性和key属性,在循环遍历处理剩下的普通属性。
  2. 将子元素挂载到props.children中。
    获取传入子元素的数量,如果大于1则转为数组并循环赋值,如果为1则直接赋值
  3. 给props属性赋默认值。
    如果defaultProps存在,则遍历defaultProps,判断传入的属性是否有值,如果没有则用defaultProps中的值替换
  4. 创建并返回ReactElement。
    调用ReactElement,其实就是将参数放在一个对象中返回了 相关源码如下:
/**
 * @description: 创建React Element元素
 * 1、分离props属性和特殊属性
 * 2、将子元素挂在到props.children上
 * 3、为props属性赋值默认值 defaultProps
 * 4、创建并返回 ReactElement
 * @param {String} type 元素类型
 * @param {Object} config 配置属性 props ref key等属性
 * @param {Array} children 子节点
 * @return {Object} ReactElement
 */
export function createElement(type, config, children) {
  // 属性名称
  // 用于后面的for循环,否则在循环中创建变量会损耗性能
  let propName;

  // Reserved names are extracted
  // 存储 React Element 中的普通元素属性,即不包含key ref source self
  const props = {};

  // 特殊属性
  let key = null;
  let ref = null;
  let self = null;
  let source = null;
  // 如果config不为空,则分离特殊属性
  if (config != null) {
    // 是否有ref属性
    if (hasValidRef(config)) {
      ref = config.ref;
      // 在开发环境
      if (__DEV__) {
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    // 如果有key
    if (hasValidKey(config)) {
      key = '' + config.key;
    }
    // 判断self和source属性
    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // Remaining properties are added to a new props object
    // 遍历config对象
    for (propName in config) {
      // 如果当前遍历到的属性是对象自身属性
      // 并且在RESERVED_PROPS对象中不存在这个属性
      // RESERVED_PROPS 存储的是特殊属性
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        // 则将满足条件的属性添加到 props 对象中(普通属性)
        props[propName] = config[propName];
      }
    }
  }

  // 处理子元素
  // 将第三个及之后的参数挂在到 props.children 属性中
  // 如果子元素是多个 props.children 是数组
  // 如果子元素是一个 props.children 是对象

  // 由于第三个参数开始以后都表示子元素
  // 所以减去前两个参数的结果就是子元素的数量
  const childrenLength = arguments.length - 2;
  // 如果子元素的数量是1 则直接赋值
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    // 如果大于1 则转为数组
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    if (__DEV__) {
      if (Object.freeze) {
        Object.freeze(childArray);
      }
    }
    props.children = childArray;
  }

  // Resolve default props
  // 处理默认值
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // 开发环境
  if (__DEV__) {
    if (key || ref) {
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      if (key) {
        // 检测有没有通过props获取key属性
        defineKeyPropWarningGetter(props, displayName);
      }
      if (ref) {
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

React16版本架构

React16版本的架构可以分为三层:调度层、协调层、渲染层。

  • Scheduler(调度层):调度任务的优先级,高优任务优先进入协调器。
  • Reconciler(协调层):构建Fiber数据结构,对比Fiber对象找出差异,记录Fiber对象要进行的DOM操作。
  • Renderer(渲染层):负责将发生变化的部分渲染到页面上。

Scheduler调度层

在React15版本中,采用了循环加递归的方式进行了VirtualDom的比对.由于递归会使用JavaScript自身的执行栈,一旦开始就无法中断,直到任务完成。如果VirtualDom树的层级比较深,VirtualDom的比对就会长期占用JavaScript的主线程,由于JavaScript又是单线程的无法同时执行其他任务,所以在比对的过程中无法响应用户操作,无法即时执行元素动画,造成了页面卡顿的现象。

在React16版本中,加入了调度层,所有任务不是直接被执行,而是加入到任务队列中,等浏览器空闲时才会执行。 而且React16版本放弃了JavaScript递归的方式进行VirtualDom的比对,而是采用循环模拟递归。而且比对的过程是利用浏览器的空闲时间完成的,没有空闲时间则会被中断,不会长期占用主线程,这就解决了VirtualDom比对造成页面卡顿的问题。

在window对象中提供了requestIdleCallback API,它可以利用浏览器的空闲时间执行任务,但是它自身也存在一些问题,比如说并不是所有的浏览器都支持,而且它的触发频率也不是很稳定,所以React最终放弃了requestIdleCallback的使用。

在React中,官方实现了自己的任务调度库,这个库就叫做Scheduler。它可以实现在浏览器空闲时执行任务,而且还可以设置任务的优先级,高级任务先执行,低优先级任务后执行。Scheduler存储在packages/scheduler文件夹中。

Reconciler协调层

在React15版本中,协调层和渲染层交替执行,即找到了差异就直接更新差异。

但在React16中,这种情况发生了变化,协调层和渲染层不再交替执行。协调层负责找出差异,在所有差异找出之后,统一交给渲染层进行DOM更新。也就是说协调层的主要任务就是找出差异部分,并为差异打上标记

Renderer渲染层

渲染层根据协调层为Fiber节点打的标记,同步执行对应的DOM操作。

既然比对的过程从递归变成了可以中断的循环,那么React是如何解决中断更新时DOM渲染不完全的问题呢?

答案是根本就不存在这个问题。因为在整个过程中,调度层和协调层的工作是在内存中完成的,是可以被打断的,而渲染层的工作被设定成不可被打断,会直接执行完渲染的任务,所以不存在DOM渲染不完全的问题。

Fiber

React 16之前的版本对比更新VirtualDom的过程时采用循环加递归实现的。这种方式有 一个问题,就是一旦任务开始进行就无法中断。如果应用中组件数量庞大,主线程被长期占用,直到整棵VirtualDom树对比更新完成之后主线程才能被释放,才能执行其他任务,这就导致一些用户交互、动画等任务无法立即得到执行,页面就会产生卡顿,非常的影响用户体验。

解决方案

  • 利用浏览器空闲时间执行任务,拒绝长时间占用主线程。
  • 放弃递只采用循环,因为循环可以被中断。
  • 任务拆分,将任务拆分成小的任务。(这样的话任务重新执行所需的代价就会小很多)

对比操作

在Fiber方案中,为了实现任务的终止再继续,DOM对比算法被分成了两部分:
1、构建Fiber (可中断)
2、提交 Commit(不可中断)
DOM初始渲染: VirtualDom => Fiber => Fiber[] => DOM DOM更新操作: newFiber vs oldFiber => Fiber[] => DOM

Fiber对象

Fiber其实就是JavaScript对象,是由VirtualDom对象演变而来的,在Fiber对象中有很多属性,如下是一些比较重要的属性:

type Fiber = {
  /****************Dom实例相关***************/
  // 标记不同的组件类型 workTag 即不同的节点类型对应的值 0-22之间的数值
  tag: workTag,
  // 组件类型  div span 组件构造函数
  type: any,
  // 实例对象,如类组件的实例,原生 DOM 实例,而function 组件没有实例,因此该属性为空
  stateNode: any,

  /************构建 Fiber 相关*************/
  // 指向自己的父级 Fiber 对象
  return: Fiber | null
  // 指向自己的第一个子集 Fiber 对象
  child: Fiber | null,
  // 指向自己的下一个兄弟 Fiber 对象
  sibling: Fiber | null,

  // 在 Fiber 树更细的过程中,每个 Fiber 都会有一个跟其对应的 Fiber
  // 称之为 current <==> workInProgress
  // 在渲染完成之后他们会交换位置
  // alternate 指向当前 Fiber 在 workInProgress 树中的对应的 Fiber
  alternate: Fiber | null,

  /***************状态数据相关*************/
  // 即将更新的 props
  pendingProps: any,
  // 旧的 props
  memoizedProps: any,
  // 旧的 state
  memoizedState: any,

  /***************副作用相关**************/
  // 该 Fiber 对应的组件产生的状态更新会存放在这个队列里面
  updateQueue: UpdateQueue<any> | null,
  // 用来记录当前 Fiber 要执行的 DOM 操作
  effectTag: SideEffectTag,
  // 子树中第一个 side effect
  firstEffect: Fiber | null
  // 单链表用来快速查找下一个 side effect
  nextEffect: Fiber | null
  // 任务的过期时间
  expirationTime: ExpirationTime,
  // 当前组件及子组件处于何种渲染模式 详见 TypeOfNode
  mode: TypeOfNode
}

TypeOfNode:

export const TypeOfNode = number
// 0 同步渲染模式
export cosnt NoMode = 0b000
// 1 严格模式
export const StrictMode = 0b0001
// 10 异步渲染过渡模式
export const BlockingMode = 0b0010
// 100 异步渲染模式
export const ConcurrentMode = ob0100
// 1000 性能测试模式
export const ProfileMode = ob1000

双缓存技术

在React中,DOM的更新采用了双缓存技术,双缓存技术致力于更快速的DOM更新。

什么是双缓存?举个例子,使用canvas绘制动画时,在绘制每一帧前都会清除上一帧的画面,清除上一帧需要花费时间,如果当前贞画面计算量又比较大,又需要花费比较长的时间,这就导致上一帧清除到下一帧显示中间会有较长的间隙,就会出现白屏。

为了解决这个问题,可以在内存中绘制当前帧动画,绘制完毕之后直接用当前帧替换上一帧画面,这样的话在帧画面替换的过程中就会节约非常多的时间,就不会出现白屏问题。这种在内存中构建并直接替换的技术叫做双缓存

React使用双缓存技术完成Fiber树的构建与替换,实现DOM对象的快速更新。

在React中最多会同时存在两棵Fiber树,当前在屏幕中显示的内容对应的Fiber树叫做current Fiber树,当发生更新时,React会在内存中重新构建一棵新的Fiber树,这棵正在构建的Fiber树叫做workInProgress Fiber树。在双缓存技术中,workInProgress Fiber树就是即将要显示在页面中的Fiber树,当这棵Fiber构建完成之后,React会直接替换掉current Fiber树达到快速更新DOM的目的,因为workInProgress Fiber树是在内存中构建的,所以它的构建速度是非常快的。

一旦workInProgress Fiber树在屏幕上呈现,它就会变成current Fiber树。

在current FIber节点对象中有一个alternate属性指向对应的workInProgress Fiber节点对象,在workInProgress Fiber节点中有一个alternate属性也指向current Fiber节点对象。 1617292075(1).jpg
如图: rootFiber表示的是组件的挂载点对应的Fiber对象,即id为root的div所对应的Fiber对象。

React在初始渲染时会先构建这个div所对应的Fiber对象,构建完成之后就将这个Fiber对象看作是current Fiber树,之后会在这个Fiber对象中添加一个alternate属性,属性值为current Fiber树的拷贝,将拷贝出的fiber树作为workInProgress Fiber树。当然在workInProgress Fiber树中也添加了alternate属性,属性值指向的是current Fiber树。

接下来构建子集fiber对象的工作就全部在workInProgress Fiber树中完成了。即app组件p节点的构建。

当所有fiber节点的构建完成之后,就由workInProgress Fiber树替换掉current Fiber树,这样就完成了Fiber节点的构建替换了。

替换完成之后,workInProgress Fiber树就变成了current Fiber树。Fiber节点对象中是存储了对应的DOM节点对象的,即DOM构建是在内存中完成的。当所有的Fiber对象构建完成之后,所有的DOM对象也就构建完成了,这时就可以用内存中的DOM对象替换页面中的DOM对象。

这就是React中使用到的双缓存技术,目的即是实现更快速的DOM更新

关于fiberRoot和rootFiber

fiberRoot表示Fiber数据结构对象,是Fiber数据结构中的最外层对象。
rootFiber表示组件挂载点对应的Fiber对象,比如React应用中默认的组件挂载点就是id为root的div
fiberRoot包含rootFiber,在fiberRoot对象中有一个current属性,存储rootFiber
rootFiber指向fiberRoot,在rootFiber对象中有一个stateNode属性,指向fiberRoot

在React应用中fiberRoot只有一个,而rootFiber可以有多个,因为render方法是可以调用多次的,每次调用render方法时,render方法的第二个参数就是rootFiber。

fiberRoot会记录应用的更新信息,比如协调器在完成工作后,会将成果存储在fiberRoot中。
如图:

1617438448(1).jpg

React是如何渲染到页面中的

要将React元素渲染到页面中,分为两个阶段:

  • render阶段
  • commit阶段

render阶段

render阶段就是协调层负责的阶段,在这个阶段中,要为每一个React元素构建对应的Fiber对象,在构建Fiber对象的过程中,还要为此Fiber对象创建对应的DOM对象,并且给Fiber对象添加effectTag属性,标注当前Fiber对象对应的DOM对象要进行的操作。
新构建的Fiber对象称之为workInProgress Fiber树,也可以理解为待提交的Fiber树,当render阶段结束之后,会被保存在fiberRoot对象中之后就会进入到commit阶段。

在commit阶段,会先获取到render阶段的工作成果,即获取到保存到fiberRoot中的workInProgress Fiber树,之后就是根据Fiber对象中的effectTag属性进行相应的DOM操作。

相关源码及调用关系如下

// 源码路径:src\react\packages\react-dom\src\client\ReactDOMLegacy.js
/**
 * @description: 渲染入口
 * @param {*} element 要渲染的ReactElement
 * @param {*} container 渲染容器
 * @param {*} callback 渲染完成之后执行的回调函数
 * @return {*}
 */
export function render(
  element: React$Element<any>,
  container: Container,
  callback: ?Function,
) {
  // 检测 container 是否是符合要求的渲染容器
  // 即检测 container 是否是真实的DOM对象
  // 如果不符合要求就报错 
  invariant(
    isValidContainer(container),
    'Target container is not a DOM element.',
  );
  if (__DEV__) {
    const isModernRoot =
      isContainerMarkedAsRoot(container) &&
      container._reactRootContainer === undefined;
    if (isModernRoot) {
      console.error(
        'You are calling ReactDOM.render() on a container that was previously ' +
          'passed to ReactDOM.createRoot(). This is not supported. ' +
          'Did you mean to call root.render(element)?',
      );
    }
  }
  // 渲染子树到container中
  return legacyRenderSubtreeIntoContainer(
    // 代表父组件,因为此处是初始渲染,是没有父组件的,传递null占位
    null,
    element,
    container,
    // 是否为服务器端渲染  true: 是服务端渲染,false不是
    // 如果是服务端渲染则需要复用 container 内部的dom元素
    false,
    callback,
  );
}

legacyRenderSubtreeIntoContainer方法的作用是将子树渲染到container容器中,初始化Fiber数据结构并创建fiberRoot和rootFiber对象。
相关源码如下:

// src\react\packages\react-dom\src\client\ReactDOMLegacy.js
/**
 * @description: 将子树渲染到容器中 (初始化Fiber数据结构: 创建 fiberRoot 及rootFiber)
 * @param {*} parentComponent 父组件 初始渲染传入了null
 * @param {*} children render方法中的第一个参数,要渲染的 ReactElement
 * @param {*} container 渲染容器
 * @param {*} forceHydrate true 为服务端渲染  false为客户端渲染
 * @param {*} callback 组件渲染完成之后的回调函数
 * @return {*}
 */
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {
  if (__DEV__) {
    topLevelUpdateWarnings(container);
    warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
  }
  // 检测 container是否是已经初始化的容器
  // react 在初始化渲染时会为最外层容器添加 _reactRootContainer 属性
  // react 会根据次属性进行不同的渲染方式
  // root 不存在表示初始渲染
  // root 存在 表示更新
  let root: RootType = (container._reactRootContainer: any);
  let fiberRoot;
  // 初始化渲染操作
  if (!root) {
    // 初始化根 Fiber 数据结构
    // 为 container 容器添加 _reactRootContainer 属性
    // 在 _reactRootContainer 对象中有一个属性叫做 _internalRoot
    // _internalRoot 属性即为 FiberRoot  表示根节点 Fiber 数据结构
    // legacyCreateRootFromDOMContainer
    // createLegacyRoot
    // new ReactDOMBlockingRoot -> this._internalRoot
    // createRootImpl

    // 其实就是在创建 fiberRoot以及rootFiber并为这两个对象添加一些默认属性
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // Initial mount should not be batched.
    unbatchedUpdates(() => {
      updateContainer(children, fiberRoot, parentComponent, callback);
    });
  } else {
    // 更新操作
    fiberRoot = root._internalRoot;
    if (typeof callback === 'function') {
      const originalCallback = callback;
      callback = function() {
        const instance = getPublicRootInstance(fiberRoot);
        originalCallback.call(instance);
      };
    }
    // Update
    updateContainer(children, fiberRoot, parentComponent, callback);
  }
  // 返回实例对象
  return getPublicRootInstance(fiberRoot);
}

legacyCreateRootFromDOMContainer中初始化了根Fiber对象的数据结构,为container容器添加了_reactRootContainer属性,相关代码如下:

// src\react\packages\react-dom\src\client\ReactDOMLegacy.js
/**
 * @description: 判断是否为服务端渲染 如果不是则清空 container 容器中的节点
 * @param {*} container dom对象
 * @param {*} forceHydrate 是否是服务端渲染
 * @return {*}
 */
function legacyCreateRootFromDOMContainer(
  container: Container,
  forceHydrate: boolean,
): RootType {
  // 检测是否是服务端渲染
  const shouldHydrate =
    forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
  // 如果不是
  if (!shouldHydrate) {
    let warned = false;
    // 
    let rootSibling;
    // 开启循环 删除 container 容器中的节点
    while ((rootSibling = container.lastChild)) {
      if (__DEV__) {
        if (
          !warned &&
          rootSibling.nodeType === ELEMENT_NODE &&
          (rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
        ) {
          warned = true;
          console.error(
            'render(): Target node has markup rendered by React, but there ' +
              'are unrelated nodes as well. This is most commonly caused by ' +
              'white-space inserted around server-rendered markup.',
          );
        }
      }
      // 删除子元素
      container.removeChild(rootSibling);
    /**
     * 为什么要清除 container 中的元素?
     * 有时需要在 container 中放置一些占位图或者 loading 图以提高首屏加载用户体验
     * 就无可避免的要向 container 中添加html标记
     * 在将 ReactElement 渲染到 container 之前,必然要先清空 container
     * 因为占位图和 ReactElement 不能同时显示
     * 
     * 所以在加入占位代码时,最好只有一个父级元素,可以减少内部代码的循环次数以提高性能
     */  
    }
  }
  if (__DEV__) {
    if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
      warnedAboutHydrateAPI = true;
      console.warn(
        'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
          'will stop working in React v17. Replace the ReactDOM.render() call ' +
          'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
      );
    }
  }
  // createLegacyRoot方法通过实例化ReactDOMBlockingRoot 类创建LegacyRoot
  return createLegacyRoot(
    container,
    shouldHydrate
      ? {
          hydrate: true,
        }
      : undefined,
  );
}

createLegacyRoot代码如下:

// src\react\packages\react-dom\src\client\ReactDOMRoot.js
/**
 * @description: 通过它可以创建 LegacyRoot 的Fiber数据结构
 * @param {*} container dom对象
 * @param {*} options
 * @return {*}
 */
export function createLegacyRoot(
  container: Container,
  options?: RootOptions,
): RootType {
  return new ReactDOMBlockingRoot(container, LegacyRoot, options);
}

ReactDOMBlockingRoot方法中只是初始化了container的_internalRoot属性值,即fiberRoot对象,并在fiberRoot对象的current属性指向rootFiber对象

// src\react\packages\react-dom\src\client\ReactDOMRoot.js
/**
 * @description: 通过它可以创建 LegacyRoot 的Fiber数据结构
 * @param {*}
 * @return {*}
 */
function ReactDOMBlockingRoot(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  this._internalRoot = createRootImpl(container, tag, options);
}

在createRootImpl中调用了createContainer去生成一些对象,其中包含fiberRoot和rootFiber:

// src\react\packages\react-dom\src\client\ReactDOMRoot.js
function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // Tag is either LegacyRoot or Concurrent Root
  // 服务端渲染相关
  const hydrate = options != null && options.hydrate === true;
  const hydrationCallbacks =
    (options != null && options.hydrationOptions) || null;
  // 此处生成 fiberRoot及rootFiber
  const root = createContainer(container, tag, hydrate, hydrationCallbacks);
  markContainerAsRoot(root.current, container);
  // 服务端渲染相关
  if (hydrate && tag !== LegacyRoot) {
    const doc =
      container.nodeType === DOCUMENT_NODE
        ? container
        : container.ownerDocument;
    eagerlyTrapReplayableEvents(container, doc);
  }
  return root;
}

而fiberRoot和rootFiber是在createContainer中调用的createFiberRoot方法生成的:

// src\react\packages\react-reconciler\src\ReactFiberReconciler.js
export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
  return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);
}
// src\react\packages\react-reconciler\src\ReactFiberRoot.js
export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  if (enableSuspenseCallback) {
    root.hydrationCallbacks = hydrationCallbacks;
  }

  // 创建根节点对应的 rootFiber
  const uninitializedFiber = createHostRootFiber(tag);
  // 为 rootFiber 添加 current 属性 值为 rootFiber
  root.current = uninitializedFiber;
  // 为 rootFiber 添加 stateNode 属性,值为 fiberRoot
  uninitializedFiber.stateNode = root;
  // 为 fiber 对象添加 updateQueue 属性,初始化 updateQueue 对象
  // updateQueue 用于存放 Update 对象
  // Update 对象用于记录组件状态的改变
  initializeUpdateQueue(uninitializedFiber);

  return root;
}

创建任务并存放于任务队列

在updateContainer方法(调用顺序为:render->legacyRenderSubtreeIntoContainer->updateContainer)中最核心的事情是创建一个任务对象,比如渲染DOM。在任务创建完成后会将任务放置在一个任务队列当中,之后就会等待浏览器的空闲时间,当有空闲时间时就会执行这个任务。相关源码如下:

// src\react\packages\react-reconciler\src\ReactFiberReconciler.js
/**
 * @description: 计算任务的过期时间,再根据任务过期时间创建Update任务,通过任务的过期时间还可以计算出任务的优先级
 * @param {*} element 要渲染的 ReactElement 对象
 * @param {*} container Fiber Root 对象
 * @param {*} parentComponent 父组件 初始渲染为 null
 * @param {*} callback 渲染完成执行的函数
 * @return {*}
 */
export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  // container 获取 rootFiber

  // current  currentTime  suspenseConfig 是为了计算任务的过期时间
  const current = container.current;
  // 获取当前距离 react 应用初始化的时间
  const currentTime = requestCurrentTimeForUpdate();
  // 异步加载设置
  const suspenseConfig = requestCurrentSuspenseConfig();
  // 计算过期时间
  // 为防止任务因为优先级原因一直被打断而未能被执行
  // react 会设置一个过期时间,当时间到了过期时间的时候
  // 如果任务还未执行的话,react将强制执行该任务
  // 初始化渲染时,任务同步执行不涉及被打断问题
  // 过期时间设置成了 1073741823 是固定的 这个表示当前任务为同步任务
  const expirationTime = computeExpirationForFiber(
    currentTime,
    current,
    suspenseConfig,
  );
  // 设置FiberRoot.context 首次执行返回一个 emptyContext是一个{}
  const context = getContextForSubtree(parentComponent);
  // 初始渲染时 Fiber Root 对象中的context属性值为null
  // 所以会进入到if中
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }
  // 创建一个待执行任务
  const update = createUpdate(expirationTime, suspenseConfig);
  // 将要更新的内容挂载到更新对象中的payload中
  // 将要更新的组件存储至payload中,方便以后读取
  update.payload = {element};

  callback = callback === undefined ? null : callback;
  // 判断callback是否存在
  if (callback !== null) {
    // 将callback挂载到update对象中
    // 其实就是一层层传递 方便 ReactElement 元素渲染完成调用
    // 回调函数执行完成后会被清除 可以在代码的后面加上return进行验证
    update.callback = callback;
  }
  // 将update对象加入到当前Fiber的更新队列中(updateQueue)
  // 待执行的任务都会被存储在 fiber.updateQueue.shared.pending中
  enqueueUpdate(current, update);
  // 调度和更新 current 对象
  scheduleWork(current, expirationTime);
  // 返回过期时间
  return expirationTime;
}

将任务放在任务队列的相关源码如下:

/**
 * @description: 将任务存放于任务队列中,创建单向链表结构存放  update next 用来串联update
 * @param {*}
 * @return {*}
 */
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
  // 获取当前 Fiber 的更新队列
  const updateQueue = fiber.updateQueue;
  // 如果更新队列不存在,就返回null
  if (updateQueue === null) {
    // 仅发生在 fiber已经被卸载
    return;
  }
  // 获取待执行的 Update 任务
  // 初始渲染时没有待执行的任务
  const sharedQueue = updateQueue.shared;
  const pending = sharedQueue.pending;
  if (pending === null) {
    // This is the first update. Create a circular list.
    update.next = update;
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  // 将 Update 任务存储在 pending 属性中
  sharedQueue.pending = update;

  if (__DEV__) {
    if (
      currentlyProcessingQueue === sharedQueue &&
      !didWarnUpdateInsideUpdate
    ) {
      console.error(
        '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;
    }
  }
}

任务执行前的准备工作

在scheduleWork方法中(调用顺序为render->legacyRenderSubtreeIntoContainer->updateContainer->scheduleWork)所做的最核心的事情是判断当前任务是否是同步任务。如果是同步任务,则调用同步任务入口函数。 相关源码如下:

// scheduleWork方法其实就是
// src\react\packages\react-reconciler\src\ReactFiberWorkLoop.js下的scheduleUpdateOnFiber方法
/**
 * @description: 判断任务是否是同步任务,如果是同步任务则调用同步入口
 * @param {*}
 * @return {*}
 */
export function scheduleUpdateOnFiber(
  fiber: Fiber,
  expirationTime: ExpirationTime,
) {
  /**
   * fiber:初始化渲染时为 rootFiber,即 <div id="root"></div>对应的fiber对象
   * expirationTime:任务过期时间 => 1073741823
   */
  /**
   * 判断是否是无限循环的 update 如果是就报错
   * 在 componentWillUpdate 或者 componentDidUpdate 生命周期函数中重复调用
   * setState 方法时,可能会发生这种情况,React限制了嵌套更新的数量(50)以防止无限循环
   * 限制的嵌套更新数量为50,可通过 NESTED_UPDATE_LIMIT 全局变量获取
   */
  checkForNestedUpdates();
  // 开发环境中执行的
  warnAboutRenderPhaseUpdatesInDEV(fiber);
  // 更新子节点中的过期时间,返回 FiberRoot
  const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
  if (root === null) {
    // 开发环境下执行  忽略
    warnAboutUpdateOnUnmountedFiberInDEV(fiber);
    return;
  }
  // 判断是否有高优先级任务打断当前正在执行的任务
  // 初始化渲染时不会执行因为初始化渲染时并没有更高优先级的任务
  checkForInterruption(fiber, expirationTime);
  // 报告调度更新,测试环境执行,忽略
  recordScheduleUpdate();
  // 获取当前调度任务的优先级 数值类型 90开始 数值越大优先级越高
  // 97 是普通优先级任务
  const priorityLevel = getCurrentPriorityLevel();
  // 判断任务是否是同步任务  Sync 的值为: 1073741823
  if (expirationTime === Sync) {
    if (
      // 检查是否处于非批量更新模式
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      // 检查是否没有处于正在渲染的任务
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // 在根上注册待处理的交互,以免丢失跟踪的交互数据
      // 初始渲染时内部条件判断不成立,内部代码没有得到执行
      schedulePendingInteractions(root, expirationTime);
      // 同步任务入口点
      performSyncWorkOnRoot(root);
    } else {
      ensureRootIsScheduled(root);
      schedulePendingInteractions(root, expirationTime);
      if (executionContext === NoContext) {
        // Flush the synchronous work now, unless we're already working or inside
        // a batch. This is intentionally inside scheduleUpdateOnFiber instead of
        // scheduleCallbackForFiber to preserve the ability to schedule a callback
        // without immediately flushing it. We only do this for user-initiated
        // updates, to preserve historical behavior of legacy mode.
        flushSyncCallbackQueue();
      }
    }
  } else {
    ensureRootIsScheduled(root);
    schedulePendingInteractions(root, expirationTime);
  }
  // 初始渲染不执行
  if (
    (executionContext & DiscreteEventContext) !== NoContext &&
    // Only updates at user-blocking priority or greater are considered
    // discrete, even inside a discrete event.
    (priorityLevel === UserBlockingPriority ||
      priorityLevel === ImmediatePriority)
  ) {
    // This is the result of a discrete event. Track the lowest priority
    // discrete update per root so we can flush them early, if needed.
    if (rootsWithPendingDiscreteUpdates === null) {
      rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
    } else {
      const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
      if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
        rootsWithPendingDiscreteUpdates.set(root, expirationTime);
      }
    }
  }
}

构建workInProgress Fiber树的rootFiber

因为初始渲染属于同步任务,所以其入口为performSyncWorkOnRoot函数(src\react\packages\react-reconciler\src\ReactFiberWorkLoop.js)。

当调用这个方法后,就说明已经正式进入了render阶段,为每一个react元素构建workInProgress Fiber树中的Fiber对象。
相关源码如下:

// src\react\packages\react-reconciler\src\ReactFiberWorkLoop.js
/**
 * @description: 同步任务入口点,进入render阶段,构建 workInProgress Fiber 树
 * @param {*} root currentFiber 树当中的 Fiber 对象
 * @return {*}
 */
function performSyncWorkOnRoot(root) {
  // ...
  if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
    // 构建 workInProgress Fiber 树及rootFiber 对象
    prepareFreshStack(root, expirationTime);
    startWorkOnPendingInteractions(root, expirationTime);
  }
  // ...
}
/**
 * @description: 构建 workInProgress Fiber 树 及 rootFiber 对象
 * @param {*} root
 * @param {*} expirationTime
 * @return {*}
 */
 
function prepareFreshStack(root, expirationTime) {
  root.finishedWork = null;
  root.finishedExpirationTime = NoWork;

  const timeoutHandle = root.timeoutHandle;
  // 初始化渲染不执行 timeoutHandle => -1 noTimeout => -1
  if (timeoutHandle !== noTimeout) {
    // The root previous suspended and scheduled a timeout to commit a fallback
    // state. Now that we have additional work, cancel the timeout.
    root.timeoutHandle = noTimeout;
    // $FlowFixMe Complains noTimeout is not a TimeoutID, despite the check above
    cancelTimeout(timeoutHandle);
  }
  // 初始化渲染不执行 workInProgress 全局变量 初始化为null
  if (workInProgress !== null) {
    let interruptedWork = workInProgress.return;
    while (interruptedWork !== null) {
      unwindInterruptedWork(interruptedWork);
      interruptedWork = interruptedWork.return;
    }
  }
  // 构建 workInProgress Fiber 树的 fiberRoot 对象
  workInProgressRoot = root;
  // 构建 workInProgress Fiber 树中的rootFiber
  workInProgress = createWorkInProgress(root.current, null);
  renderExpirationTime = expirationTime;
  workInProgressRootExitStatus = RootIncomplete;
  workInProgressRootFatalError = null;
  workInProgressRootLatestProcessedExpirationTime = Sync;
  workInProgressRootLatestSuspenseTimeout = Sync;
  workInProgressRootCanSuspendUsingConfig = null;
  workInProgressRootNextUnprocessedUpdateTime = NoWork;
  workInProgressRootHasPendingPing = false;

  if (enableSchedulerTracing) {
    spawnedWorkDuringRender = null;
  }

  if (__DEV__) {
    ReactStrictModeWarnings.discardPendingWarnings();
  }
}

其中构建workInProgress Fiber树中的rootFiber是通过createWorkInProgress创建的。

// src\react\packages\react-reconciler\src\ReactFiber.js
// 构建 workInProgress Fiber 树 中的 rootFiber
// 构建完成后会替换current fiber
// 初始渲染 pendingProps 为 null
export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {
  // current: current Fiber 中的 rootFiber
  // 获取currentFiber 对应的 workInProgress Fiber
  let workInProgress = current.alternate;
  // 如果 workInProgress 不存在
  if (workInProgress === null) {
    // 创建 fiber 对象
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    );
    // 属性复用
    workInProgress.elementType = current.elementType;
    workInProgress.type = current.type;
    workInProgress.stateNode = current.stateNode;

    if (__DEV__) {
      // DEV-only fields
      if (enableUserTimingAPI) {
        workInProgress._debugID = current._debugID;
      }
      workInProgress._debugSource = current._debugSource;
      workInProgress._debugOwner = current._debugOwner;
      workInProgress._debugHookTypes = current._debugHookTypes;
    }
    // 使用 alternate 存储 current
    workInProgress.alternate = current;
    // 使用 alternate 存储 workInProgress
    current.alternate = workInProgress;
  } else {
    workInProgress.pendingProps = pendingProps;

    // We already have an alternate.
    // Reset the effect tag.
    workInProgress.effectTag = NoEffect;

    // The effect list is no longer valid.
    workInProgress.nextEffect = null;
    workInProgress.firstEffect = null;
    workInProgress.lastEffect = null;

    if (enableProfilerTimer) {
      // We intentionally reset, rather than copy, actualDuration & actualStartTime.
      // This prevents time from endlessly accumulating in new commits.
      // This has the downside of resetting values for different priority renders,
      // But works for yielding (the common case) and should support resuming.
      workInProgress.actualDuration = 0;
      workInProgress.actualStartTime = -1;
    }
  }
  // 属性复用
  workInProgress.childExpirationTime = current.childExpirationTime;
  workInProgress.expirationTime = current.expirationTime;

  workInProgress.child = current.child;
  workInProgress.memoizedProps = current.memoizedProps;
  workInProgress.memoizedState = current.memoizedState;
  workInProgress.updateQueue = current.updateQueue;

  // Clone the dependencies object. This is mutated during the render phase, so
  // it cannot be shared with the current fiber.
  const currentDependencies = current.dependencies;
  workInProgress.dependencies =
    currentDependencies === null
      ? null
      : {
          expirationTime: currentDependencies.expirationTime,
          firstContext: currentDependencies.firstContext,
          responders: currentDependencies.responders,
        };

  // These will be overridden during the parent's reconciliation
  workInProgress.sibling = current.sibling;
  workInProgress.index = current.index;
  workInProgress.ref = current.ref;

  if (enableProfilerTimer) {
    workInProgress.selfBaseDuration = current.selfBaseDuration;
    workInProgress.treeBaseDuration = current.treeBaseDuration;
  }

  if (__DEV__) {
    workInProgress._debugNeedsRemount = current._debugNeedsRemount;
    switch (workInProgress.tag) {
      case IndeterminateComponent:
      case FunctionComponent:
      case SimpleMemoComponent:
        workInProgress.type = resolveFunctionForHotReloading(current.type);
        break;
      case ClassComponent:
        workInProgress.type = resolveClassForHotReloading(current.type);
        break;
      case ForwardRef:
        workInProgress.type = resolveForwardRefForHotReloading(current.type);
        break;
      default:
        break;
    }
  }

  return workInProgress;
}

workLoopSync方法解析

rootFiber指的就是id为root的div所对应的Fiber对象,当rootFiber对象构建完成之后,就需要构建其子集Fiber对象。子集的Fiber对象就是render方法的第一个参数,即element。而element的子集Fiber对象是通过workLoopSync方法构建的。

在workLoopSync方法中,通过一个while循环不断创建子集的Fiber,直到创建结果为null,这样就构建了除rootFiber之外的所有子集的Fiber对象。

// src\react\packages\react-reconciler\src\ReactFiberWorkLoop.js
/**
 * @description: 构建除rootFiber之外的所有子集fiber对象
 * @param {*}
 * @return {*}
 */
function workLoopSync() {
  // workInProgress是一个fiber对象
  // 它的值不为 null 意味着该fiber对象上仍然有要更新的要执行
  // while方法支撑render阶段,所有 fiber 节点的构建
  // Already timed out, so perform work without checking if we need to yield.
  while (workInProgress !== null) {
    workInProgress = performUnitOfWork(workInProgress);
  }
}

performUnitOfWork解析

在workLoopSync方法中通过调用performUnitOfWork来构建除rootFiber之外的其余子级Fiber对象。在react16版本中采用循环的方式来模拟递归完成Fiber节点的构建,即beginWork是从父到子构建,而completeUnitOfWork是从子到父构建Fiber对象。

// src\react\packages\react-reconciler\src\ReactFiberWorkLoop.js
/**
 * @description: 通过while循环构建子集Fiber对象
 * @param {*} unitOfWork
 * @return {*}
 */
function performUnitOfWork(unitOfWork: Fiber): Fiber | null {
  const current = unitOfWork.alternate;
  startWorkTimer(unitOfWork);
  setCurrentDebugFiberInDEV(unitOfWork);

  let next;
  // 初始渲染未执行
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, renderExpirationTime);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    // beginWork 从父到子 构建Fiber
    next = beginWork(current, unitOfWork, renderExpirationTime);
  }

  resetCurrentDebugFiberInDEV();
  // 为旧的 props 属性赋值
  // 此次更新后 pendingProps 变为 memoizedProps
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // 从子到父 构建其余节点 Fiber 对象
    next = completeUnitOfWork(unitOfWork);
  }

  ReactCurrentOwner.current = null;
  return next;
}

其中调用的beginWork方法是从父到子级来构建Fiber对象。在beginWork方法中,根据父节点的tag类型来构建不同类型的子节点的Fiber对象,其中rootFiber的tag为3,所以会先调用类型3所对应的updateHostRoot方法。

/**
 * @description: 从上到下构建 Fiber
 * @param {*}
 * @return {*}
 */
function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderExpirationTime: ExpirationTime,
): Fiber | null {
  const updateExpirationTime = workInProgress.expirationTime;
  // 判断是否有旧的 Fiber 对象
  // 初始化渲染时 只有 rootFiber 节点存在 current
  // if (current !== null)
  // ...
  // NoWork 常量 值为0 清空过期时间
  workInProgress.expirationTime = NoWork;
  // 根据当前 Fiber 的类型决定如何构建起子级 Fiber 对象
  // 文件位置: shared/ReactWorkTags.js
  console.log(workInProgress.tag)
  switch (workInProgress.tag) {
    // 2
    // 函数组件第一次渲染时使用
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderExpirationTime,
      );
    }
    // 16
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateExpirationTime,
        renderExpirationTime,
      );
    }
    // 0
    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,
      );
    }
    // 1
    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,
      );
    }
    // 3
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderExpirationTime);
    // 5
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderExpirationTime);
    // ...
  }
}

而在updateHostRoot方法中,首先获取了workInProgress的更新队列,根据更新队列来更新rootFiber的子元素。

// src\react\packages\react-reconciler\src\ReactFiberBeginWork.js
/**
 * @description: 更新 hostRoot
 * @param {*} current
 * @param {*} workInProgress
 * @param {*} renderExpirationTime
 * @return {*}
 */
function updateHostRoot(current, workInProgress, renderExpirationTime) {
  pushHostRootContext(workInProgress);
  // 获取更新队列
  const updateQueue = workInProgress.updateQueue;
  // 获取新的props对象
  const nextProps = workInProgress.pendingProps;
  // 获取上一次渲染使用的 state
  const prevState = workInProgress.memoizedState;
  // 获取上一次渲染使用的 children
  const prevChildren = prevState !== null ? prevState.element : null;
  // 浅复制更新队列
  // workInProgress.updateQueue 浅拷贝 current.updateQueue
  cloneUpdateQueue(current, workInProgress);
  // 获取 updateQueue.payload 赋值给 workInProgress.memoizedState
  // 要更新的内容就是element 就是 rootFiber 的子元素
  processUpdateQueue(workInProgress, nextProps, null, renderExpirationTime);
  // 获取element所在对象
  const nextState = workInProgress.memoizedState;
  // 从对象中获取 element 
  const nextChildren = nextState.element;
  // 在计算 state 后如果前两个 Children 相同的情况
  // 初始化时:
  // prevChildren => null
  // nextState => App
  if (nextChildren === prevChildren) {
    resetHydrationState();
    return bailoutOnAlreadyFinishedWork(
      current,
      workInProgress,
      renderExpirationTime,
    );
  }
  const root: FiberRoot = workInProgress.stateNode;
  // 服务端渲染相关
  if (root.hydrate && enterHydrationState(workInProgress)) {
    let child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
    workInProgress.child = child;

    let node = child;
    while (node) {
      node.effectTag = (node.effectTag & ~Placement) | Hydrating;
      node = node.sibling;
    }
  } else {
    // 构建子集 fiber 对象
    // 构建完成子集 Fiber 对象之后,会把 Fiber 对象挂载到 workInProgress.child 对象中
    reconcileChildren(
      current,
      workInProgress,
      nextChildren,
      renderExpirationTime,
    );
    resetHydrationState();
  }
  return workInProgress.child;
}

构建单个子集Fiber对象的情况

在之前已经在updateQueue中获取到了已经要渲染的子集元素,即render方法的第一个参数,对应到代码中就是传入到reconcileChildren方法中的nextChildren对象。reconcileChildren方法的作用就是构建子元素所对应的Fiber对象。
而创建子集Fiber对象是通过mountChildFibers方法调用生成的。

// src\react\packages\react-reconciler\src\ReactFiberBeginWork.js
/**
 * @description: 构建子级Fiber对象
 * @param {*} current 旧的Fiber对象 如果不是初始渲染的话,需要进行新旧Fiber对象的对比
 * @param {*} workInProgress 新Fiber对象
 * @param {*} nextChildren 子级 VDOM对象
 * @param {*} renderExpirationTime 过期时间 初始渲染时为整形最大值,代表同步任务
 * @return {*}
 */
function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderExpirationTime: ExpirationTime,
) {
  /**
   * 为什么要传递 current ?
   * 如果不是初始渲染,则要进行新旧DOM对比
   * 初始渲染时则用不到
   */
  // 如果为null 代表初始化渲染
  if (current === null) {
    // mountChildFibers真正构建了nextChildren所对应的fiber对象
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderExpirationTime,
    );
  } else {
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderExpirationTime,
    );
  }
}

而mountChildFibers方法实际上就是ChildReconciler方法。在ChildReconciler中返回的就是一个reconcileChildFibers方法,所以实际上最终调用的是reconcileChildFibers方法。

在reconcileChildFibers方法中,首先判断了子级vdom是否为占位组件。如果是占位组件,则直接使用占位组件的子元素作为newChild,否则直接使用子级vdom。

其次检查newChild是否为对象类型。因为如果是对象类型的话,说明只有一个子级节点,如果是一个数组的话,说明有多个子级节点。

因为初始化渲染时rootFiber下的子集是App组件对应的Fiber对象,是一个object类型,所以会调用reconcileSingleElement方法来生成所对应的Fiber对象,至此构建单个子集Fiber对象过程结束。

// src\react\packages\react-reconciler\src\ReactChildFiber.js
/**
   * @description: 构建子集Fiber对象
   * @param {*} returnFiber 父Fiber对象
   * @param {*} currentFirstChild 旧的第一个子 Fiber 初始化渲染时为null
   * @param {*} newChild 新的vdom对象
   * @param {*} expirationTime 过期时间 如果为整形最大值则代表同步任务
   * @return {*}
   */
  function reconcileChildFibers(
    returnFiber: Fiber,
    currentFirstChild: Fiber | null,
    newChild: any,
    expirationTime: ExpirationTime,
  ): Fiber | null {
    // 这是入口方法 根据 newChild 类型进行对应处理
    // 判断新的子 vdom 是否为占位组件 比如 <></>

    // 如果是占位组件则为true
    const isUnkeyedTopLevelFragment =
      typeof newChild === 'object' &&
      newChild !== null &&
      newChild.type === REACT_FRAGMENT_TYPE &&
      newChild.key === null;
    // 如果newChild 为占位符,使用占位符组成的子元素作为 newChild
    if (isUnkeyedTopLevelFragment) {
      newChild = newChild.props.children;
    }

    // 检测 newChild是否为对象类型
    // 如果是对象说明子元素只有一个
    // 如果是数组的话说明子元素有多个
    const isObject = typeof newChild === 'object' && newChild !== null;
    // newChild是单个对象时
    if (isObject) {
      // 匹配子元素的类型
      switch (newChild.$$typeof) {
        // 子元素为react元素的话
        case REACT_ELEMENT_TYPE:
          return placeSingleChild(
            reconcileSingleElement(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
        // 如果为 portal类型
        case REACT_PORTAL_TYPE:
          return placeSingleChild(
            reconcileSinglePortal(
              returnFiber,
              currentFirstChild,
              newChild,
              expirationTime,
            ),
          );
      }
    }
    // ...
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        expirationTime,
      );
    }
    // Remaining cases are all treated as empty.
    return deleteRemainingChildren(returnFiber, currentFirstChild);
  }

悄悄说一句,这只是初稿~后面会优化。