React核心源码解读

1. 配置 React 源码本地调试环境

  1. 使用 create-react-app 脚手架创建项目

    npx create-react-app react-test

  2. 弹射 create-react-app 脚手架内部配置

    npm run eject

  3. 克隆 react 官方源码 (在项目的根目录下进行克隆)

    git clone --branch v16.13.1 --depth=1 https://github.com/facebook/react.git src/react

  4. 链接本地源码

    // 文件位置: react-test/config/webpack.config.js
    resolve: {
      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-test/config/env.js
    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

    // 文件位置: react-test/config/webpack.config.js [babel-loader]
    plugins: [
      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 specific 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/.eslingrc.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 引入方式

    import * as React from "react"
    import * as ReactDOM from "react-dom"
    
  13. 解决 vsCode 中 flow 报错

    "javascript.validate.enable": false
    
  14. 可选项配置

    如果你的 vscode 编辑器安装了 prettier 插件并且在保存 react 源码文件时右下角出现如下错误,按照如下步骤解决

    React核心源码解读_第1张图片

    1. 全局安装 prettier

      npm i prettier -g

    2. 配置 prettier path

      Settings > Extensions > Prettier > Prettier path

  15. __DEV__ 报错

    删除 node_modules 文件夹,执行 npm install

2. 创建 React 元素

JSX 被 Babel 编译为 React.createElement 方法的调用,createElement 方法在调用后返回的就是 ReactElement,就是 virtualDOM。

2.1 createElement

文件位置:packages/react/src/ReactElement.js

/**
 * 创建 React Element
 * type      元素类型
 * config    配置属性
 * children  子元素
 * 1. 分离 props 属性和特殊属性
 * 2. 将子元素挂载到 props.children 中
 * 3. 为 props 属性赋默认值 (defaultProps)
 * 4. 创建并返回 ReactElement
 */
export function createElement(type, config, children) {
  /**
   * propName -> 属性名称
   * 用于后面的 for 循环
   */
  let propName;

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

  /**
   * 待提取属性
   * React 内部为了实现某些功能而存在的属性
   */
  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  // 如果 config 不为 null
  if (config != null) {
    // 如果 config 对象中有合法的 ref 属性
    if (hasValidRef(config)) {
      // 将 config.ref 属性提取到 ref 变量中
      ref = config.ref;
      // 在开发环境中
      if (__DEV__) {
        // 如果 ref 属性的值被设置成了字符串形式就报一个提示
        // 说明此用法在将来的版本中会被删除
        warnIfStringRefCannotBeAutoConverted(config);
      }
    }
    // 如果在 config 对象中拥有合法的 key 属性
    if (hasValidKey(config)) {
      // 将 config.key 属性中的值提取到 key 变量中
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 遍历 config 对象
    for (propName in config) {
      // 如果当前遍历到的属性是对象自身属性
      // 并且在 RESERVED_PROPS 对象中不存在该属性
    /**
    * const RESERVED_PROPS = {
      *     key: true,
      *     ref: true,
      *     _self: true,
      *     _source:true 
      *}
    */
      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 是对象类型
    props.children = children;
    // 如果子元素的数量大于 1
  } else if (childrenLength > 1) {
    // 创建数组, 数组中元素的数量等于子元素的数量
    const childArray = Array(childrenLength);
    // 开启循环 循环次匹配子元素的数量
    for (let i = 0; i < childrenLength; i++) {
      // 将子元素添加到 childArray 数组中
      // i + 2 的原因是实参集合的前两个参数不是子元素
      childArray[i] = arguments[i + 2];
    }
    // 如果是开发环境
    if (__DEV__) {
      // 如果 Object 对象中存在 freeze 方法
      if (Object.freeze) {
        // 调用 freeze 方法 冻结 childArray 数组
        // 防止 React 核心对象被修改 冻结对象提高性能
        Object.freeze(childArray);
      }
    }
    // 将子元素数组挂载到 props.children 属性中
    props.children = childArray;
  }

  /**
   * 如果当前处理是组件
   * 看组件身上是否有 defaultProps 属性
   * 这个属性中存储的是 props 对象中属性的默认值
   * 遍历 defaultProps 对象 查看对应的 props 属性的值是否为 undefined
   * 如果为undefined 就将默认值赋值给对应的 props 属性值
   */

  // 将 type 属性值视为函数 查看其中是否具有 defaultProps 属性
  if (type && type.defaultProps) {
    // 将 type 函数下的 defaultProps 属性赋值给 defaultProps 变量
    const defaultProps = type.defaultProps;
    // 遍历 defaultProps 对象中的属性 将属性名称赋值给 propName 变量
    for (propName in defaultProps) {
      // 如果 props 对象中的该属性的值为 undefined
      if (props[propName] === undefined) {
        // 将 defaultProps 对象中的对应属性的值赋值给 props 对象中的对应属性的值
        props[propName] = defaultProps[propName];
      }
    }
  }

  /**
   * 在开发环境中 如果元素的 key 属性 或者 ref 属性存在
   * 监测开发者是否在组件内部通过 props 对象获取了 key 属性或者 ref 属性
   * 如果获取了 就报错
   */

  // 如果处于开发环境
  if (__DEV__) {
    // 元素具有 key 属性或者 ref 属性
    if (key || ref) {
      // 看一下 type 属性中存储的是否是函数 如果是函数就表示当前元素是组件
      // 如果元素不是组件 就直接返回元素类型字符串
      // displayName 用于在报错过程中显示是哪一个组件报错了
      // 如果开发者显式定义了 displayName 属性 就显示开发者定义的
      // 否者就显示组件名称 如果组件也没有名称 就显示 'Unknown'
      const displayName =
        typeof type === 'function'
          ? type.displayName || type.name || 'Unknown'
          : type;
      // 如果 key 属性存在
      if (key) {
        // 为 props 对象添加key 属性
        // 并指定当通过 props 对象获取 key 属性时报错
        defineKeyPropWarningGetter(props, displayName);
      }
      // 如果 ref 属性存在
      if (ref) {
        // 为 props 对象添加 ref 属性
        // 并指定当通过 props 对象获取 ref 属性时报错
        defineRefPropWarningGetter(props, displayName);
      }
    }
  }
  // 返回 ReactElement
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    // 在 Virtual DOM 中用于识别自定义组件
    ReactCurrentOwner.current,
    props,
  );
}

2.2 ReactElement

文件位置:packages/react/src/ReactElement.js

/**
 * 接收参数 返回 ReactElement
 */
const ReactElement = function (type, key, ref, self, source, owner, props) {
  const element = {
    /**
     * 组件的类型, 十六进制数值或者 Symbol 值
     * React 在最终在渲染 DOM 的时候, 需要确保元素的类型是 REACT_ELEMENT_TYPE
     * 需要此属性作为判断的依据
     */
    $$typeof: REACT_ELEMENT_TYPE,

    /**
     * 元素具体的类型值 如果是元素节点 type 属性中存储的就是 div span 等等
     * 如果元素是组件 type 属性中存储的就是组件的构造函数
     */
    type: type,
    /**
     * 元素的唯一标识
     * 用作内部 vdom 比对 提升 DOM 操作性能
     */
    key: key,
    /**
     * 存储元素 DOM 对象或者组件 实例对象
     */
    ref: ref,
    /**
     * 存储向组件内部传递的数据
     */
    props: props,

    /**
     * 记录当前元素所属组件 (记录当前元素是哪个组件创建的)
     */
    _owner: owner,
  };
  // 返回 ReactElement
  return element;
};

2.3 hasValidRef

文件位置:packages/react/src/ReactElement.js

/**
 * 查看参数对象中是否有合法的 ref 属性
 * 返回布尔值
 */
function hasValidRef(config) {
  return config.ref !== undefined;
}

2.4 hasValidKey

文件位置:packages/react/src/ReactElement.js

/**
 * 查看参数对象中是否有合法的 key 属性
 * 返回布尔值
 */
function hasValidKey(config) {
  return config.key !== undefined;
}

2.5 isValidElement

文件位置:packages/react/src/ReactElement.js

/**
 * 验证 object 参数是否是 ReactElement. 返回布尔值
 * 验证成功的条件:
 * object 是对象
 * object 不为null
 * object对象中的 $$typeof 属性值为 REACT_ELEMENT_TYPE
 */
export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    object.$$typeof === REACT_ELEMENT_TYPE
  );
}

2.6 defineKeyPropWarningGetter

文件位置:packages/react/src/ReactElement.js

/**
 *  指定当通过 props 对象获取 key 属性时报错
 *  props        组件中的 props 对象
 *  displayName  组件名称标识
 */
function defineKeyPropWarningGetter(props, displayName) {
  // 通过 props 对象获取 key 属性报错
  const warnAboutAccessingKey = function () {
    // 在开发环境中
    if (__DEV__) {
      // specialPropKeyWarningShown 控制错误只输出一次的变量
      if (!specialPropKeyWarningShown) {
        // 通过 specialPropKeyWarningShown 变量锁住判断条件
        specialPropKeyWarningShown = true;
        // 指定报错信息和组件名称
        console.error(
          '%s: `key` is not a prop. Trying to access it will result ' +
            'in `undefined` being returned. If you need to access the same ' +
            'value within the child component, you should pass it as a different ' +
            'prop. (https://reactjs.org/link/special-props)',
          displayName,
        );
      }
    }
  };
  warnAboutAccessingKey.isReactWarning = true;
  // 为 props 对象添加 key 属性
  Object.defineProperty(props, 'key', {
    // 当获取 key 属性时调用 warnAboutAccessingKey 方法进行报错
    get: warnAboutAccessingKey,
    configurable: true,
  });
}

2.7 defineRefPropWarningGetter

文件位置:packages/react/src/ReactElement.js

/**
 *  指定当通过 props 对象获取 ref 属性时报错
 *  props        组件中的 props 对象
 *  displayName  组件名称标识
 */
function defineRefPropWarningGetter(props, displayName) {
  // 通过 props 对象获取 ref 属性报错
  const warnAboutAccessingRef = function () {
    if (__DEV__) {
      // specialPropRefWarningShown 控制错误只输出一次的变量
      if (!specialPropRefWarningShown) {
        // 通过 specialPropRefWarningShown 变量锁住判断条件
        specialPropRefWarningShown = true;
        // 指定报错信息和组件名称
        console.error(
          '%s: `ref` is not a prop. Trying to access it will result ' +
            'in `undefined` being returned. If you need to access the same ' +
            'value within the child component, you should pass it as a different ' +
            'prop. (https://reactjs.org/link/special-props)',
          displayName,
        );
      }
    }
  };

  warnAboutAccessingRef.isReactWarning = true;
  // 为 props 对象添加 key 属性
  Object.defineProperty(props, 'ref', {
    get: warnAboutAccessingRef,
    configurable: true,
  });
}

3. React 架构

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

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

3.1 Scheduler 调度层

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

在 React 16 的版本中,放弃了 JavaScript 递归的方式进行 virtualDOM 的比对,而是采用循环模拟递归。而且比对的过程是利用浏览器的空闲时间完成的,不会长期占用主线程,这就解决了 virtualDOM 比对造成页面卡顿的问题。

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

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

Scheduler 存储在 packages/scheduler 文件夹中。

3.2 Reconciler 协调层

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

3.3 Renderer 渲染层

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

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

其实根本就不存在这个问题,因为在整个过程中,调度器和协调器的工作是在内存中完成的是可以被打断的,渲染器的工作被设定成不可以被打断,所以不存在DOM 渲染不完全的问题。

4. 数据结构

4.1 Fiber

type Fiber = {
  /************************  DOM 实例相关  *****************************/
  
  // 标记不同的组件类型, 值详见 WorkTag
  tag: WorkTag,

  // 组件类型 div、span、组件构造函数
  type: any,

  // 实例对象, 如类组件的实例、原生 dom 实例, 而 function 组件没有实例, 因此该属性是空
  stateNode: any,
 
	/************************  构建 Fiber 树相关  ***************************/
  
  // 指向自己的父级 Fiber 对象
  return: Fiber | null,

  // 指向自己的第一个子级 Fiber 对象
  child: Fiber | null,
  
  // 指向自己的下一个兄弟 iber 对象
  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 | null,
  
  // 用来记录当前 Fiber 要执行的 DOM 操作
  effectTag: SideEffectTag,

  // 存储第一个要执行副作用的子级 Fiber 对象
  firstEffect: Fiber | null,
  
  // 存储下一个要执行副作用的子级 Fiber 对象
  // 执行 DOM 渲染时要先通过 first 找到第一个, 然后通过 next 一直向后查找
  nextEffect: Fiber | null,
  
  // 存储 DOM 操作完后的副作用 比如调用生命周期函数或者钩子函数的调用
  lastEffect: Fiber | null,

  // 任务的过期时间
  expirationTime: ExpirationTime,
  
	// 当前组件及子组件处于何种渲染模式 详见 TypeOfMode
  mode: TypeOfMode,
};

在这里插入图片描述

4.2 WorkTag

文件位置:packages/shared/ReactWorkTags.js

type WorkTag =
  | 0
  | 1
  | 2
  | 3
  | 4
  | 5
  | 6
  | 7
  | 8
  | 9
  | 10
  | 11
  | 12
  | 13
  | 14
  | 15
  | 16
  | 17
  | 18
  | 19
  | 20
  | 21
  | 22;

export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2;
export const HostRoot = 3;
export const HostPortal = 4;
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
export const Block = 22;

4.3 TypeOfMode

文件位置: packages/react-reconciler/src/ReactTypeOfMode.js

export type TypeOfMode = number;

// 0 同步渲染模式
export const NoMode = 0b0000;
// 1 严格模式
export const StrictMode = 0b0001;
// 10 异步渲染过渡模式
export const BlockingMode = 0b0010;
// 100 异步渲染模式
export const ConcurrentMode = 0b0100;
// 1000 性能测试模式
export const ProfileMode = 0b1000;

4.3 SideEffectTag

文件位置:packages/shared/ReactSideEffectTags.js

export type SideEffectTag = number;

// Don't change these two values. They're used by React Dev Tools.
export const NoEffect = /*              */ 0b0000000000000; // 0
export const PerformedWork = /*         */ 0b0000000000001; // 1

// You can change the rest (and add more).
export const Placement = /*             */ 0b0000000000010; // 2
export const Update = /*                */ 0b0000000000100; // 4
export const PlacementAndUpdate = /*    */ 0b0000000000110; // 6
export const Deletion = /*              */ 0b0000000001000; // 8
export const ContentReset = /*          */ 0b0000000010000; // 16
export const Callback = /*              */ 0b0000000100000; // 32
export const DidCapture = /*            */ 0b0000001000000; // 64
export const Ref = /*                   */ 0b0000010000000; // 128
export const Snapshot = /*              */ 0b0000100000000; // 256
export const Passive = /*               */ 0b0001000000000; // 512
export const Hydrating = /*             */ 0b0010000000000; // 1024
export const HydratingAndUpdate = /*    */ 0b0010000000100; // 1028

// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /*   */ 0b0001110100100; // 932

// Union of all host effects
export const HostEffectMask = /*        */ 0b0011111111111; // 2047

export const Incomplete = /*            */ 0b0100000000000; // 2048
export const ShouldCapture = /*         */ 0b1000000000000; // 4096

4.4 Update

let update: Update<*> = {
  expirationTime,
  suspenseConfig,

  tag: UpdateState,
  payload: null,
  callback: null,

  next: (null: any),
};

4.5 UpdateQueue

const queue:  = {
  // 上一次更新之后的 state, 作为下一次更新的基础
  baseState: fiber.memoizedState,
  baseQueue: null,
  shared: {
    pending: null,
  },
  effects: null,
}
fiber.updateQueue = queue;

4.6 RootTag

文件位置:packages/shared/ReactRootTags.js

export type RootTag = 0 | 1 | 2;

// ReactDOM.render
export const LegacyRoot = 0;
// ReactDOM.createBlockingRoot
export const BlockingRoot = 1;
// ReactDOM.createRoot
export const ConcurrentRoot = 2;

4.7 双缓存技术

在 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 节点对象。

React核心源码解读_第2张图片
React核心源码解读_第3张图片

4.8 区分 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 方法是可以调用多次的

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

React核心源码解读_第4张图片

5. 初始化渲染

要将 React 元素渲染到页面中,分为两个阶段,render 阶段和 commit 阶段。

render 阶段负责创建 Fiber 数据结构并为 Fiber 节点打标记,标记当前 Fiber 节点要进行的 DOM 操作。

commit 阶段负责根据 Fiber 节点标记 ( effectTag ) 进行相应的 DOM 操作。

5.1 render 阶段

5.1.1 render

文件位置:packages/react-dom/src/client/ReactDOMLegacy.js

/**
 * 渲染入口
 * element 要进行渲染的 ReactElement, createElement 方法的返回值
 * container 渲染容器 
* callback 渲染完成后执行的回调函数 */ export function render( element: React$Element, container: Container, callback: ?Function, ) { // 检测 container 是否是符合要求的渲染容器 // 即检测 container 是否是真实的DOM对象 // 如果不符合要求就报错 invariant( isValidContainer(container), 'Target container is not a DOM element.', ); return legacyRenderSubtreeIntoContainer( // 父组件 初始渲染没有父组件 传递 null 占位 null, element, container, // 是否为服务器端渲染 false 不是服务器端渲染 true 是服务器端渲染 false, callback, ); }

5.1.2 isValidContainer

文件位置:packages/react-dom/src/client/ReactDOMRoot.js

/**
 * 判断 node 是否是符合要求的 DOM 节点
 * 1. node 可以是元素节点
 * 2. node 可以是 document 节点
 * 3. node 可以是 文档碎片节点
 * 4. node 可以是注释节点但注释内容必须是 react-mount-point-unstable
 * 		react 内部会找到注释节点的父级 通过调用父级元素的 insertBefore 方法, 将 element 插入到注释节点的前面
 */
export function isValidContainer(node: mixed): boolean {
  return !!(
    node &&
    (node.nodeType === ELEMENT_NODE ||
      node.nodeType === DOCUMENT_NODE ||
      node.nodeType === DOCUMENT_FRAGMENT_NODE ||
      (node.nodeType === COMMENT_NODE &&
        (node: any).nodeValue === ' react-mount-point-unstable '))
  );
}

5.1.3 初始化 FiberRoot

React核心源码解读_第5张图片
render方法一共三个参数,是三个参数是回调函数,this指向第一个参数对象的dom对象

console.log(this) // 
Hello
5.1.3.1 legacyRenderSubtreeIntoContainer

文件位置: packages/react-dom/src/client/ReactDOMLegacy.js

/**
 * 将子树渲染到容器中 (初始化 Fiber 数据结构: 创建 fiberRoot 及 rootFiber)
 * parentComponent: 父组件, 初始渲染传入了 null
 * children: render 方法中的第一个参数, 要渲染的 ReactElement
 * container: 渲染容器
 * forceHydrate: true 为服务端渲染, false 为客户端渲染
 * callback: 组件渲染完成后需要执行的回调函数
 **/
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component,
  children: ReactNodeList,
  container: Container,
  forceHydrate: boolean,
  callback: ?Function,
) {
  /**
   * 检测 container 是否已经是初始化过的渲染容器
   * react 在初始渲染时会为最外层容器添加 _reactRootContainer 属性
   * react 会根据此属性进行不同的渲染方式
   * root 不存在 表示初始渲染
   * root 存在 表示更新
   */
  // 获取 container 容器对象下是否有 _reactRootContainer 属性
  let root: RootType = (container._reactRootContainer: any);
  // 即将存储根 Fiber 对象
  let fiberRoot;
  if (!root) {
    // 初始渲染
    // 初始化根 Fiber 数据结构
    // 为 container 容器添加 _reactRootContainer 属性
    // 在 _reactRootContainer 对象中有一个属性叫做 _internalRoot
    // _internalRoot 属性值即为 FiberRoot 表示根节点 Fiber 数据结构
    // legacyCreateRootFromDOMContainer
    // createLegacyRoot
    // new ReactDOMBlockingRoot -> this._internalRoot
    // createRootImpl
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate,
    );
    // 获取 Fiber Root 对象
    fiberRoot = root._internalRoot;
    /**
     * 改变 callback 函数中的 this 指向
     * 使其指向 render 方法第一个参数的真实 DOM 对象
     */
    // 如果 callback 参数是函数类型
    if (typeof callback === 'function') {
      // 使用 originalCallback 存储 callback 函数
      const originalCallback = callback;
      // 为 callback 参数重新赋值
      callback = function () {
        // 获取 render 方法第一个参数的真实 DOM 对象
        // 实际上就是 id="root" 的 div 的子元素
        // rootFiber.child.stateNode
        // rootFiber 就是 id="root" 的 div
        const instance = getPublicRootInstance(fiberRoot);
        // 调用 callback 函数并改变函数内部 this 指向
        originalCallback.call(instance);
      };
    }
    // 初始化渲染不执行批量更新
    // 因为批量更新是异步的是可以被打断的, 但是初始化渲染应该尽快完成不能被打断
    // 所以不执行批量更新
    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);
  }
  // 返回 render 方法第一个参数的真实 DOM 对象作为 render 方法的返回值
  // 就是说渲染谁 返回谁的真实 DOM 对象
  return getPublicRootInstance(fiberRoot);
}

React核心源码解读_第6张图片

5.1.3.2 legacyCreateRootFromDOMContainer

文件位置: packages/react-dom/src/client/ReactDOMLegacy.js

/**
 * 判断是否为服务器端渲染 如果不是服务器端渲染
 * 清空 container 容器中的节点
 */
function legacyCreateRootFromDOMContainer(
  container: Container,
  forceHydrate: boolean,
): RootType {
  // container => 
// 检测是否为服务器端渲染 const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container); // 如果不是服务器端渲染 if (!shouldHydrate) { let rootSibling; // 开启循环 删除 container 容器中的节点 while ((rootSibling = container.lastChild)) { // 删除 container 容器中的节点 container.removeChild(rootSibling); /** * 为什么要清除 container 中的元素 ? * 为提供首屏加载的用户体验, 有时需要在 container 中放置一些占位图或者 loading 图 * 就无可避免的要向 container 中加入 html 标记. * 在将 ReactElement 渲染到 container 之前, 必然要先清空 container * 因为占位图和 ReactElement 不能同时显示 * * 在加入占位代码时, 最好只有一个父级元素, 可以减少内部代码的循环次数以提高性能 *
*

placement

*

placement

*

placement

*

*/ } } return createLegacyRoot( container, shouldHydrate ? { hydrate: true, } : undefined, ); }
5.1.3.3 createLegacyRoot

文件位置: packages/react-dom/src/client/ReactDOMRoot.js

/**
 * 通过实例化 ReactDOMBlockingRoot 类创建 LegacyRoot
 */
export function createLegacyRoot(
  container: Container,
  options?: RootOptions,
): RootType {
  // container => 
// LegacyRoot 常量, 值为 0, // 通过 render 方法创建的 container 就是 LegacyRoot return new ReactDOMBlockingRoot(container, LegacyRoot, options); }
5.1.3.3 ReactDOMBlockingRoot

文件位置: packages/react-dom/src/client/ReactDOMRoot.js

/**
 * 类, 通过它可以创建 LegacyRoot 的 Fiber 数据结构
 */
function ReactDOMBlockingRoot(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // tag => 0 => legacyRoot
  // container => 
// container._reactRootContainer = {_internalRoot: {}} this._internalRoot = createRootImpl(container, tag, options); }
5.1.3.4 createRootImpl

文件位置: packages/react-dom/src/client/ReactDOMRoot.js


function createRootImpl(
  container: Container,
  tag: RootTag,
  options: void | RootOptions,
) {
  // container => 
// tag => 0 // options => undefined const root = createContainer(container, tag, hydrate, hydrationCallbacks); markContainerAsRoot(root.current, container); return root; }
5.1.3.5 createContainer

文件位置: packages/react-reconciler/src/ReactFiberReconciler.js

// 创建 container
export function createContainer(
  containerInfo: Container,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): OpaqueRoot {
  // containerInfo => 
// tag: 0 // hydrate: false // hydrationCallbacks: null // 忽略了和服务器端渲染相关的内容 return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks); }
5.1.3.6 createFiberRoot

文件位置: packages/react-reconciler/src/ReactFiberRoot.js

// 创建根节点对应的 fiber 对象
export function createFiberRoot(
  containerInfo: any,
  tag: RootTag,
  hydrate: boolean,
  hydrationCallbacks: null | SuspenseHydrationCallbacks,
): FiberRoot {
  // 创建 FiberRoot
  const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);
  // 创建根节点对应的 rootFiber
  const uninitializedFiber = createHostRootFiber(tag);
  // 为 fiberRoot 添加 current 属性 值为 rootFiber
  root.current = uninitializedFiber;
  // 为 rootFiber 添加 stateNode 属性 值为 fiberRoot
  uninitializedFiber.stateNode = root;
  // 为 fiber 对象添加 updateQueue 属性, 初始化 updateQueue 对象
  // updateQueue 用于存放 Update 对象
  // Update 对象用于记录组件状态的改变
  initializeUpdateQueue(uninitializedFiber);
  // 返回 root
  return root;
}
5.1.3.7 FiberRootNode

文件位置: packages/react-reconciler/src/ReactFiberRoot.js

function FiberRootNode(containerInfo, tag, hydrate) {
  this.tag = tag;
  this.current = null;
  this.containerInfo = containerInfo;
  this.pendingChildren = null;
  this.pingCache = null;
  this.finishedExpirationTime = NoWork;
  this.finishedWork = null;
  this.timeoutHandle = noTimeout;
  this.context = null;
  this.pendingContext = null;
  this.hydrate = hydrate;
  this.callbackNode = null;
  this.callbackPriority = NoPriority;
  this.firstPendingTime = NoWork;
  this.firstSuspendedTime = NoWork;
  this.lastSuspendedTime = NoWork;
  this.nextKnownPendingLevel = NoWork;
  this.lastPingedTime = NoWork;
  this.lastExpiredTime = NoWork;
  if (enableSchedulerTracing) {
    this.interactionThreadID = unstable_getThreadID();
    this.memoizedInteractions = new Set();
    this.pendingInteractionMap = new Map();
  }
  if (enableSuspenseCallback) {
    this.hydrationCallbacks = null;
  }
}
5.3.1.8 initializeUpdateQueue

文件位置: packages/react-reconciler/src/ReactFiberRoot.js

export function initializeUpdateQueue(fiber: Fiber): void {
  const queue: UpdateQueue = {
    baseState: fiber.memoizedState,
    baseQueue: null,
    shared: {
      pending: null,
    },
    effects: null,
  };
  fiber.updateQueue = queue;
}

你可能感兴趣的:(reactjs)