Fancy Front End Fancy Front End
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)

Jonsam NG

让有意义的事变得有意思,让有意思的事变得有意义
  • 开始上手
  • 基础
  • 调度器(Scheduler)
  • 更新器(Updater)
  • 渲染器(Render)
  • 更新周期
  • hooks 原理
  • 总结
  • 📙 React源码漂流记
  • 开始上手
  • 基础
  • reactivity
  • runtime-core
  • runtime-dom
  • Awesome Web
  • Awesome NodeJS
话题
  • 导航
  • Q&A
  • 幻灯片
  • 关于
  • 分类
  • 标签
  • 归档
博客 (opens new window)
GitHub (opens new window)
  • 开始上手
  • plan 计划
  • 基础

  • 调和(Reconciliation)

  • 调度器(Scheduler)

  • 更新器(Updater)

  • 渲染器(Render)

  • hooks原理

  • 总结

  • React源码漂流记

    • 开始上手
    • Plan 计划
    • 前言
    • React 源码漂流记:ReactElement 与基础概念
      • 目录
      • 源码结构
      • React API 概况
      • JSX
      • ReactElement
      • isValidElement
      • VDOM
      • 扩展
      • 问题
      • 总结
    • React 源码漂流记:ReactChildren 与节点操纵
    • React 源码漂流记:React 整体结构和理念初认识
    • React 源码漂流记:React 调和器核心源码解读(一)
    • React 源码漂流记:React 调和器核心源码解读(二)
    • React 源码漂流记:React 调和器核心源码解读(三)
    • React 源码漂流记:React 调和器核心源码解读(四)
    • React 源码漂流记:React 调和器核心源码解读(五)
    • React 源码漂流记:React 调和器核心源码解读(六)
    • React 源码漂流记:React 调和器核心源码解读(七)
    • React 源码漂流记:React 调和器核心源码解读(八)
    • React 源码漂流记:React 调和器核心源码解读(九)
    • React 源码漂流记:React 调和器核心源码解读(十)
    • React 源码漂流记:React 调度器核心源码解读(一)
    • 带着原理重读 React 官方文档(一)
    • 带着原理重读 React 官方文档(二)
  • react
  • React源码漂流记
jonsam
2022-04-14
目录

React 源码漂流记:ReactElement 与基础概念

标签: React17精简

# 目录

  • 目录
    • 学习目标
  • 源码结构
  • React API 概况
  • JSX
    • 介绍
    • 为什么使用 JSX?
    • JSX 如何 解析为 JS?
  • ReactElement
    • createElement
  • isValidElement
  • VDOM
  • 扩展
    • React 的详细目录结构和作用
    • 为什么 React 17 之前需要显式引入 React,17 版本就不需要了呢?
    • React 中的 VDOM 是 ReactElement 吗?
    • owner 是如何连接 ReactElement 和 Fiber 的?owner 有什么作用?
  • 问题
    • VDOM Tree 和 Fiber Tree 是如何连接的?
    • 为什么用 className?
    • ReactElement Tree、Fiber Tree 和 DOM Tree 的关系?
  • 总结

# 学习目标

  • 学习 React 的整体目录结构、API 概况、核心包的作用。
  • 学习 JSX、ReactElement、VDOM 等概念,了解 JSX 的解析原理。
  • 了解 React 选择 JSX 和 VDOM 的原因。

# 源码结构

熟悉 React 的小伙伴可能都知道,React 大致上可以分成调和器、调度器、渲染器几个部分。对应到 React 的源码里,最重要的就是有四个包,分别是 react、react-dom、scheduler、react-reconciler。克隆下源码,大概像是这样:

React 源码包结构

上述几个包的核心作用:

  • react:导出 React 的核心 API,供外部应用使用。比如 Fragment、forwardRef、memo、hook 全家桶等。
  • react-dom:React 基于 web 的渲染层,导出一些渲染相关的 API,比如说 render、createPortal、createRoot 等。
  • scheduler:React 中的调度器,负责任务队列的维护,基于优先级调度任务。
  • react-reconciler:React 中的调和器,负责 React 渲染的整体流程,包括 FiberTree 的调和等,与调度器配合完成更新任务的包装与调度、捕获与冒泡过程、DIFF 算法、EffectTag List 的维护、维护 FiberTree 双缓存结构、组件生命周期的调用、配合渲染器完成 DOM 渲染等。

# React API 概况

在 react 包中 React.js 文件中对 React 有如下定义,通过这个定义,我们可以对 React 的核心 API 初步认识。

const React = {
  // 提供了用于处理 children 不透明数据结构的实用方法。
  // 操作 ReactChildren 的方法。ReactChildren不是数组。这里模拟数组的一些方法。
  Children: { 
    map,
    forEach,
    count,
    toArray,
    only,
  },
  // 创建一个能够通过 ref 属性附加到 React 元素的 ref。
  createRef, 
  // 定义类组件需集成自 Component
  Component,
  // PureComponent 以浅层对比 prop 和 state 的方式来实现 shouldComponentUpdate 函数。
  PureComponent, 
  // 创建一个 Context 对象。当组件订阅了这个 Context 对象,组件会从组件树中离自身最近的那个匹配的 Provider 中读取到当前的 context 值。
  createContext,
  // forwardRef 会创建一个React组件,这个组件能够将其接受的 ref 属性转发到其组件树下的另一个组件中。
  forwardRef, 
  // lazy 函数能让你像渲染常规组件一样处理动态引入(的组件)。
  lazy, 
  // memo 检查 props 变更,以此通过记忆组件渲染结果的方式来提高组件的性能表现。
  memo,
  // Hook API
  // 返回一个 memoized 回调函数。
  useCallback,
  // 接收一个 context 对象,并返回距离 <MyContext.Provider> 最近的 context 的当前值。
  useContext,
  // 接收一个包含命令式、且可能有副作用代码的函数完成副作用操作。React 的纯函数式世界通往命令式世界的逃生通道
  useEffect,
  // 在使用 ref 时自定义暴露给父组件的实例值。
  useImperativeHandle,
  // 可用于在 React 开发者工具中显示自定义 hook 的标签。
  useDebugValue,
  // 所有的 DOM 变更之后同步调用 effect。可以使用它来读取 DOM 布局并同步触发重渲染。在浏览器执行绘制之前,useLayoutEffect 内部的更新计划将被同步刷新。
  useLayoutEffect,
  // 返回一个 memoized 值。
  useMemo,
  // useState 的替代方案。它接收一个 reducer,并返回当前的 state 以及与其配套的 dispatch 方法。
  useReducer,
  // useRef 返回一个可变的 ref 对象,其 .current 属性被初始化为传入的参数,返回的 ref 对象在组件的整个生命周期内持续存在。
  useRef,
  // 返回一个 state,以及更新 state 的函数。
  useState,
  // Fragments 允许你将子列表分组,而无需向 DOM 添加额外节点。
  Fragment: REACT_FRAGMENT_TYPE,
  // Profiler 测量一个 React 应用多久渲染一次以及渲染一次的“代价”。
  Profiler: REACT_PROFILER_TYPE,
  // StrictMode 是一个用来突出显示应用程序中潜在问题的工具。
  StrictMode: REACT_STRICT_MODE_TYPE, 
  // Suspense 可以指定加载指示器,以防其组件树中的某些子组件尚未具备渲染条件。
  Suspense: REACT_SUSPENSE_TYPE, // 与lazy结合使用,指定一个feedback。
  // 创建并返回指定类型的新 React 元素。
  createElement: __DEV__ ? createElementWithValidation : createElement,
  // 以 element 元素为样板克隆并返回新的 React 元素。
  cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
  // 验证对象是否为 React 元素
  isValidElement: isValidElement,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

其中比较重要的大致为:

  • 元素相关:Children、createElement、cloneElement。
  • 组件相关:Component、PureComponent、createRef、Fragment、forwardRef
  • hooks api: useCallback,useContext,useEffect,useImperativeHandle,useDebugValue,useLayoutEffect,useMemo,useReducer,useRef,useState。
  • 优化相关:lazy、memo、Suspense。
  • 其他:createContext。

# JSX

# 介绍

什么是 JSX?

JSX (JavaScript Syntax Extension,JavaScript 语法扩展) 是 JavaScript 语法的扩展,最初用于 React,它提供一种类似于 HTML 的语法来 结构化的编写组件。【来自 JSX (opens new window)】

文档

JSX 可以生成 React “元素”,可以很好地描述 UI 应该呈现出它应有交互的本质形式,具有 JavaScript 的全部功能。

React 开发者对于 JSX 应该是很熟悉了,更专业一点来说:

  • JSX 是一种将 JS 和 HTML 混合编写组件的语法糖,其语法需要通过 babel 解析之后才能被浏览器识别。
  • JSX 语法可以通过 @babel/plugin-transform-react-jsx-source (opens new window) 插件进行解析。

# 为什么使用 JSX?

关于 JSX 更详细的内容,React 官方文档中 JSX 简介 (opens new window) 已经讲的很清楚了,此处不再赘述。但是我想浅谈一下我对 React 选择 JSX 背后的哲学原因的理解。

React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合。

React 并没有采用将标记与逻辑进行分离到不同文件这种人为地分离方式,而是通过将二者共同存放在称之为 “组件” 的松散耦合单元之中,来实现关注点分离。【来自 React 官方文档:为什么使用 JSX? (opens new window)】

  • 渲染逻辑与 UI 逻辑耦合 是 React、Vue 等框架流行带来的组件化理念的必然结果。组件化要求我们将样式(UI 逻辑)与行为(渲染逻辑)封装到组件中已达到代码复用之目的,这也是组件化开发带来的最大的红利。
  • 将标记与逻辑进行分离到不同文件 这种思维方式在框架诞生之前编写原生 JS 时最常用的代码分离的手段,当然框架的组件化驱动的理念促使这种做法被放弃,取而代之的有如下的两种常见的新的理念,这两种理念都达到了 将二者共同存放在称之为“组件”的松散耦合单元之中,来实现关注点分离 的目的。
    • JSX:以 React.js、Solid.js 框架为代表。将 HTML 和 JavaScript 混合编写组件。
    • SFC:以 Vue.js 和 svelte.js 框架为代表。单文件组件将组件分为 template 、 style 和 script 三个部分。

# JSX 如何 解析为 JS?

让我们使用 babel-transform-react-jsx (opens new window) 对如下的代码进行转换:

const work = () => {dosomething();}
const Conponent = () => {
 return (
    <div style={{color: '#ffffff'}}>
      <h1 class="title">heading</h1>
      <div class="body" onClick={work}>content</div>
    </div>
    )
}
1
2
3
4
5
6
7
8
9

转换结果如下:

const work = () => {
  dosomething();
};

const Conponent = () => {
  return /*#__PURE__*/ React.createElement(
    "div",
    {
      style: {
        color: "#ffffff"
      }
    },
    /*#__PURE__*/ React.createElement(
      "h1",
      {
        class: "title"
      },
      "heading"
    ),
    /*#__PURE__*/ React.createElement(
      "div",
      {
        class: "body",
        onClick: work
      },
      "content"
    )
  );
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

分析上面的解析过程可知:

  • babel 插件在解析 jsx 代码时,js 部分是不需要解析的,html 部分会被解析为 React.createElement 语法。
  • React.createElement 会被加上 /*#__PURE__*/ 的静态内容标记。
  • 多个子节点并不是通过数组传入而是以多个参数的形式传入的,这个可以通过 rest 运算符处理。

JSX 会将代码中 html 转化为渲染函数(如 vue 中的 h 函数、createElement 函数)的语法糖,以方便框架对 JSX 的内容进行处理。这实际上使得 JSX 语法与框架解耦,使 JSX 能够运用到各种实现了渲染函数的框架之中。

# ReactElement

下面将介绍 ReactElement 以及 VDOM 的概念。

# createElement

createElement 创建 React 元素(ReactElement)。先来看一个例子,假如一个经过 babel 解析过的 JSX 代码如下:

React.createElement("div", {
    class: "class_name",
    id: "id_name",
    key: "key_name",
    ref: "ref_name"
}, React.createElement("span", null, "Tom"), React.createElement("span", null, "Jerry"));
1
2
3
4
5
6

以上的执行结果如下:

{
    $$typeof: REACT_ELEMENT_TYPE,
    type:'div',
    key: 'key_name',
    ref: "ref_name",
    props: {
        class: "class_name",
        id: "id_name",
        children: [
            React.createElement("span", null, "Tom"),
            React.createElement("span", null, "Jerry")
        ]
    }
     _owner: ReactCurrentOwner.current,
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

这便是 ReactElement 的真面目了。React 是基于 VDOM 的运行时框架,其内部节点的创建、更新、patch、删除都是通过 VDOM 来实现的。

我们通常熟知的 VDOM 节点,包括 type、attr、children 三个元素,那么我们再来看 ReactElement 的特征,ReactElement 对象也包含了这三个属性,只不过 attr 和 children 是放在 props 中的,我们知道 React 组件的设计哲学是 组件是依赖 props 和 state更新,props 关注组件与外部的状态,state 关注组件内部的状态 ,这一点也是符合理念的。

下面的内容我们将从源码的角度继续探究:

createElement 的源码:

// src/react/packages/react/src/ReactElement.js
// 根据元素类型 type,元素属性 config 和元素子节点(数组) children 创建 react 元素
export function createElement(type, config, children) {
  let propName;
  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    // 检查是否添加了 ref 属性
    if (hasValidRef(config)) {
      ref = config.ref;
    }
    // 检查是否添加了 key 属性
    if (hasValidKey(config)) {
      key = '' + config.key;
    }

    self = config.__self === undefined ? null : config.__self;
    source = config.__source === undefined ? null : config.__source;
    // 添加至属性对象
    for (propName in config) {
      if (
        hasOwnProperty.call(config, propName) &&
        !RESERVED_PROPS.hasOwnProperty(propName)
      ) {
        props[propName] = config[propName];
      }
    }
  }
  // 计算 children 的长度,children 是作为剩余参数传入的
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    // 单一子节点直接赋值
    // children 是放到 props 上的,因此可以通过 props 的 children 获得组件内部内容
    props.children = children;
  } else if (childrenLength > 1) {
    const childArray = Array(childrenLength);
    for (let i = 0; i < childrenLength; i++) {
      childArray[i] = arguments[i + 2];
    }
    // 多个子节点转为数组
    props.children = childArray;
  }

  // 元素默认的属性
  if (type && type.defaultProps) {
    const defaultProps = type.defaultProps;
    for (propName in defaultProps) {
      if (props[propName] === undefined) {
        props[propName] = defaultProps[propName];
      }
    }
  }
  // 调用工厂函数创建 ReactElement。
  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

如上函数具有如下的核心功能:

  • 计算 key、ref、self、source 属性。
  • 计算 props,包括 configs 中除 RESERVED_PROPS 之外的属性、children 属性、当前类型的 ReactElement 所应该具有的默认属性 (如 'div' 元素的默认属性)。
  • 调用 ReactElement 创建 ReactElement。

下面我们接着看下 ReactElement 工厂函数,这对于我们了解 React 虚拟 DOM 的结构至关重要:

const ReactElement = function (type, key, ref, self, source, owner, props) {
  // 新建一个ReactElement对象
  const element = {
    // ReactElement 的独一无二的标志,用来判断 element 是否是 ReactElement。
    $$typeof: REACT_ELEMENT_TYPE,
    // element 的类型
    type: type,
    // element 的 key 值,这对 React 复用元素很重要
    key: key,
    // element 的 ref 属性,元素的引用
    ref: ref,
    // element 的属性,包括了 children、class、id 等
    props: props,
    // element 的属主,当前元素所属于的 Fiber,由哪一个 Fiber 所创建。
    _owner: owner,
  };

  return element;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  • $$typeof :ReactElement 的独一无二的标志,用来判断 element 是否是 ReactElement, REACT_ELEMENT_TYPE = symbolFor('react.element') 。
  • type:element 的类型,注意如 'div'、'span' 等。
  • key:element 的 key 值,这对 React 复用元素很重要。
  • ref::element 的 ref 属性,元素的引用。
  • props:element 的属性,包括了 children、class、id 等。
  • _owner:element 的属主,当前元素所属于的 Fiber,由哪一个 Fiber 所创建。

# isValidElement

isValidElement 判断 element 是否是合法的 ReactElement。

export function isValidElement(object) {
  return (
    typeof object === 'object' &&
    object !== null &&
    // $$typeof: Symbol(react.element)
    object.$$typeof === REACT_ELEMENT_TYPE 
  );
}
1
2
3
4
5
6
7
8

# VDOM

VDOM 是对 DOM(Document Object Model)的一种轻量级的 JavaScript 呈现方式,多用于 React、Vue 等声明式的前端框架中。使用 VDOM 有如下的优点:

  • 轻量级。VDOM 只需要记录很少的信息就能展示 DOM 呈现方式和结构。
  • 速度更快。VDOM 能够对连续更新做批量处理,减少 reflow 和 repaint。
  • 抽象层,跨平台。VDOM 在 DOM Tree 的基础上抽象出 VDOM Tree,VDOM Tree 是一种数据结构,不依赖于平台特性。这使得核心逻辑能够运行在不同的平台上,屏蔽掉平台的兼容性。
  • 可控制,可优化。因为 VDOM 足够简单,JavaScript 能够很方便的操纵和控制 UI 展现,如删除、增加、更新、移动节点等。同时,对于页面状态的更新有了更加可控的优化手段,如 DIFF 算法、节点复用。

当然 VDOM 也存在一些问题:

  • 初始化的时间成本。初始化时需要将 UI 的展现转化为具体的 VDOM Tree,这部分的转换需要一定的时间成本。
  • DIFF 算法的成本。尽管 JavaScript 操作 VDOM 的效率足够高,但是在非常大的 VDOM Tree 的结构面前,DIFF 的成本就显得很重要。尽管各个框架针对 VDOM 使用了各种的优化手段,如 React 中基于链表的单向 DIFF、Vue 中基于数组的双向 DIFF、基于 key 和 type 比较的节点复用等,DIFF 的成本都是 VDOM 的速度瓶颈所在。

下图是某个节点的全部属性,可见 DOM 是很 “重” 的。

DOM 很重

# 扩展

# React 的详细目录结构和作用

packages.
├── create-subscription
├── dom-event-testing-library
├── eslint-plugin-react-hooks
├── jest-mock-scheduler
├── jest-react
├── react // 核心 API
├── react-art // 平台相关,用于 canvas, svg
├── react-cache
├── react-client
├── react-debug-tools
<!-- devtools 相关 -->
├── react-devtools
├── react-devtools-core
├── react-devtools-extensions
├── react-devtools-inline
├── react-devtools-shared
├── react-devtools-shell
├── react-devtools-timeline
├── react-dom // 平台相关,用于 web 环境
├── react-fetch
├── react-fs
├── react-interactions
├── react-is
├── react-native-renderer // 平台相关,用于 ReactNative
├── react-noop-renderer
├── react-pg
├── react-reconciler // 调和器相关
├── react-refresh
<!-- SSR 相关 -->
├── react-server
├── react-server-dom-relay
├── react-server-dom-webpack
├── react-server-native-relay
├── react-suspense-test-utils
├── react-test-renderer
├── scheduler // 调度器相关
├── shared
├── use-subscription
└── use-sync-external-store
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

# 为什么 React 17 之前需要显式引入 React,17 版本就不需要了呢?

在上面 JSX 的编译过程中,我们可以看到到,JSX 实际上不是浏览器所能够识别的,需要 babel、或者 TS 等工具来进行解析,那么解析之后的结果当然就是带着渲染函数语法糖的 JS 代码,所以如果没引入 React,通常会报 ReferenceError: React is not defined 的错误。

在 React17 中官方与 babel 合作,引入了全新的的 JSX 的转化。

React 17 在 React 的 package 中引入了两个新入口,这些入口只会被 Babel 和 TypeScript 等编译器使用。新的 JSX 转换不会将 JSX 转换为 React.createElement,而是自动从 React 的 package 中引入新的入口函数并调用。【参考:React 官网:介绍全新的 JSX 转换 (opens new window)】

这里将渲染函数与 React 框架进行了解耦,由编译工具从 React 中引入渲染函数,完成编译 JSX 的目标。

// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('h1', { children: 'Hello world' });
}
1
2
3
4
5
6

编译 JSX 的工具会自动引入渲染函数 jsx ,我们不在需要自己引入。

# React 中的 VDOM 是 ReactElement 吗?

个人认为,ReactElement 只是 React 中 VDOM 的一部分,另外一部分是后面要展开的 Fiber。React 依赖 Fiber 来达到异步可中断的 concurrent 模式更新的目标,同时依赖 ReactDOM 来描述组件 UI 的状态。

VDOM 本质上是 DOM 元素的 JavaScript 抽象和描述。ReactElement 描述了 DOM 的静态类型、属性和结构,Fiber 则在此基础上描述组件的渲染状态、更新链表、针对 DOM 的 Effect Tag、调度更新的优先级等。

# _owner 是如何连接 ReactElement 和 Fiber 的?_owner 有什么作用?

由上面的分析可以看出,_owner 的赋值其实是 ReactCurrentOwner.current 的值,对 ReactCurrentOwner.current 的赋值可以追到 finishClassComponent 函数中:

// src/react/packages/react-reconciler/src/ReactFiberBeginWork.new.js
ReactCurrentOwner.current = workInProgress;
1
2

而 finishClassComponent 主要在 updateClassComponent 、 mountIncompleteClassComponent 和 mountIndeterminateComponent 中 ClassComponent 的部分中调用,可见 _owner 实际上是用于类组件的。继续追 workInProgress,发现 workInProgress 是在 performUnitOfWork 函数中赋值的:

function performUnitOfWork(unitOfWork: Fiber): void {
  let next;
  next = beginWork(current, unitOfWork, subtreeRenderLanes);

  if (next === null) {
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }

  ReactCurrentOwner.current = null;
}
1
2
3
4
5
6
7
8
9
10
11
12

这个函数将在调和器的部分详细讲,现在可以清楚的是 workInProgress 表示当前调和器正在处理的 Fiber,在渲染类组件时将 workInProgress 记录到 React 内部共享变量 ReactCurrentOwner.current 中,此时 createElement 时就能够获取到当前 ReactElement 所属的 Fiber 了。

_owner 的作用:

  1. 通过 element._owner 查询到 element 所属的 Fiber 和 组件。

例如,在检查 element.children 的子元素是否具有 key 值的 validateExplicitKey 函数中有如下代码:

if (
    element &&
    element._owner &&
    element._owner !== ReactCurrentOwner.current
  ) {
    // 获取此元素所属的组件的名称
    childOwner = ` It was passed a child from ${getComponentNameFromType(
      element._owner.type,
    )}.`;
  }
1
2
3
4
5
6
7
8
9
10

# 问题

# VDOM Tree 和 Fiber Tree 是如何连接的?

Fiber 中有一个属性 stateNode 存储当前 Fiber 所对应的组件的渲染模板,执行这个模板就可以得到 VDOM Tree。后文详述。

下面以首次渲染过程为例说明两者之间的关系:

  1. JSX 组件将会编译为带渲染函数的 js 模板(渲染模板);
  2. 调用 ReactDOM.render 创建 FiberRoot 和 HostRootFiber,并生成首次更新的同步任务,同步任务立即执行;
  3. 渲染任务被回调,开始渲染,调和 FiberTree,挂载(更新)组件树;
  4. 在挂载(更新)组件时执行渲染模板,createElement 在运行时被层层调用,生成 ReactElement Tree,也就是 VDOM Tree;
  5. VDOM Tree 转化为 DOM Tree,渲染节点到屏幕。
  6. 新的渲染任务被回调时,回到 3。

# 为什么用 className?

参考:为什么 Vue 的 JSX 中的 class 属性用了 class,而 React 却用了 className? (opens new window)

# ReactElement Tree、Fiber Tree 和 DOM Tree 的关系?

# 总结

本文主要讲解 React Element 与基础概念,总结重点如下:

  • 阅读 React 源码的原因、方法和意义。
  • React 中源码的目录结构和核心包的作用。
  • React API 的概况。
  • JSX 的解析原理。
  • ReactElement 创建过程,以及各个属性的含义。
  • VDOM 的概念和优缺点。
编辑 (opens new window)
上次更新: 2022/08/24, 18:21:13
前言
React 源码漂流记:ReactChildren 与节点操纵

← 前言 React 源码漂流记:ReactChildren 与节点操纵→

最近更新
01
渲染原理之组件结构与 JSX 编译
09-07
02
计划跟踪
09-06
03
开始上手
09-06
更多文章>
Theme by Vdoing | Copyright © 2022-2022 Fancy Front End | Made by Jonsam by ❤
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式