chst365's blog chst365's blog
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

chst365

DIV工程师
首页
  • Git
  • 网络
  • 操作系统
  • 浏览器
  • webpack
  • JavaScript
  • TypeScript
  • 性能
  • 工程化
  • React
  • 编程题
  • React技术揭秘
  • 算法
  • Node
  • 编码解码
  • NodeJS系列
  • Linux系列
  • JavaScript系列
  • HTTP系列
  • GIT系列
  • ES6系列
  • 设计模式系列
  • CSS系列
  • 小程序系列
  • 数据结构与算法系列
  • React系列
  • Vue3系列
  • Vue系列
  • TypeScript系列
  • Webpack系列
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 浏览器

  • webpack

  • TypeScript

  • 性能

  • 工程化

  • React

  • JavaScript

  • 编程题

  • React技术揭秘

    • React理念
    • React新老架构
    • Fiber
    • 前置知识
      • 源码的文件结构
        • 顶层目录
      • 调试源码
        • 拉取源码
        • 创建项目
      • 深入理解JSX
        • JSX简介
        • React.createElement
        • React Component
        • JSX与Fiber节点
    • render阶段
    • commit阶段
    • Diff算法
    • 状态更新
    • Hooks
  • 算法

  • 前端
  • React技术揭秘
chst365
2022-09-16
目录

前置知识

# 源码的文件结构

# 顶层目录

除去配置文件和隐藏文件夹,根目录的文件夹有三个:

根目录
├── fixtures        # 包含一些给贡献者准备的小型 React 测试项目
├── packages        # 包含元数据(比如 package.json)和 React 仓库中所有 package 的源码(子目录 src)
├── scripts         # 各种工具链的脚本,比如git、jest、eslint等
1
2
3
4

这里关注packages目录

# react文件夹 (opens new window)

React的核心,包含所有全局的API,如:

  • React.createElement
  • React.Component
  • React.Children 这些API全平台通用,但不包含ReactDOM、ReactNative等平台特定的代码。在npm上作为单独包 (opens new window)发布。

# scheduler文件夹 (opens new window)

Scheduler(调度器)的实现

# shared文件夹 (opens new window)

源码中其他模块公用的方法和全局变量,如shared/ReactSymbols.js (opens new window)中保存React不同组件类型的定义。

// ...
export let REACT_ELEMENT_TYPE = 0xeac7;
export let REACT_PORTAL_TYPE = 0xeaca;
export let REACT_FRAGMENT_TYPE = 0xeacb;
// ...
1
2
3
4
5

# Renderer相关的文件夹

- react-art
- react-dom                 # 注意这同时是DOM和SSR(服务端渲染)的入口
- react-native-renderer
- react-noop-renderer       # 用于debug fiber(后面会介绍fiber)
- react-test-renderer
1
2
3
4
5

# 实验性包的文件夹

React将自己流程中的一部分抽离出来,形成可独立使用的包。 由于是实验性质,所不建议在生产中用。

- react-server        # 创建自定义SSR流
- react-client        # 创建自定义的流
- react-fetch         # 用于数据请求
- react-interactions  # 用于测试交互相关的内部特性,比如React的事件模型
- react-reconciler    # Reconciler的实现,你可以用他构建自己的Renderer
1
2
3
4
5

# 辅助包的文件夹

React将一些辅助功能形成单独包

- react-is       # 用于测试组件是否是某类型
- react-client   # 创建自定义的流
- react-fetch    # 用于数据请求
- react-refresh  # “热重载”的React官方实现
1
2
3
4

# react-reconciler文件夹 (opens new window)

重点关注这个包,虽然它是一个实验包,内部很多功能在正式版还未开发,但它一边对接Scheduler,一边对接不同平台的Renderer,构成了React16的架构体系。

# 调试源码

即使版本号相同(当前最新版为17.0.0 RC),但是facebook/react项目master分支的代码和我们使用create-react-app创建的项目node_modules下的react项目代码还是有些区别。

因为React的新代码都是直接提交到master分支,而create-react-app内的react使用的是稳定版的包。

为了始终使用最新版React教学,我们调试源码遵循以下步骤:

  1. 从facebook/react项目master分支拉取最新源码
  2. 基于最新源码构建react、scheduler、react-dom三个包
  3. 通过create-react-app创建测试项目,并使用步骤2创建的包作为项目依赖的包

# 拉取源码

# 拉取代码
git clone https://github.com/facebook/react.git

# 如果拉取速度很慢,可以考虑如下2个方案:

# 1. 使用cnpm代理
git clone https://github.com.cnpmjs.org/facebook/react

# 2. 使用码云的镜像(一天会与react同步一次)
git clone https://gitee.com/mirrors/react.git
1
2
3
4
5
6
7
8
9
10

安装依赖

# 切入到react源码所在文件夹
cd react

# 安装依赖
yarn
1
2
3
4
5

打包react、scheduler、react-dom三个包为dev环境可以使用的cjs包。

# 执行打包命令
yarn build react/index,react/jsx,react-dom/index,scheduler --type=NODE
1
2

在源码目录build/node_modules下会生成最新代码的包。为react、react-dom创建yarn link。 通过yarn link可改变项目中依赖包的目录指向

cd build/node_modules/react
# 申明react指向
yarn link
cd build/node_modules/react-dom
# 申明react-dom指向
yarn link
1
2
3
4
5
6

# 创建项目

通过create-react-app创建项目,并将react,react-dom包指向刚才生成的包。

npx create-react-app a-react-demo
# 将项目内的react react-dom指向之前申明的包
yarn link react react-dom
1
2
3

现尝试在react/build/node_modules/react-dom/cjs/react-dom.development.js中随意打印些东西。 启动项目,可在浏览器控制台看到打印输出的东西了。

# 深入理解JSX

JSX作为描述组件内容的数据结构,为JS赋予了更多视觉表现力。 那么,有些疑问:

  • JSX和Fiber是同一个东西吗
  • React Component、React Element是同一个东西吗,和JSX有什么关系?

# JSX简介

官网对其的描述 (opens new window)。 JSX在编译时会被Babel编译为React.createElement方法。 这也是为什么在每个使用JSX的文件中,必须显式声明,否则在运行时该模块会报react未定义的错,不过React17已解决了此问题,无需显式声明。 详见JSX转换 (opens new window)

import React from 'react';
1

JSX 并不只能被编译为React.createElement方法,可通过@babel/plugin-transform-react-jsx (opens new window)插件告诉Babel编译时需将JSX编译为什么函数的调用(默认为React.createElement) 如在parent这个类react的库中 ,JSX会被编译为h的函数调用

// 编译前
<p>KaSong</p>
// 编译后
h("p", null, "KaSong");
1
2
3
4

# React.createElement

JSX 被编译为React.createElement都做了什么:

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) {
    // 将 config 处理后赋值给 props
    // ...省略
  }

  const childrenLength = arguments.length - 2;
  // 处理 children,会被赋值给props.children
  // ...省略

  // 处理 defaultProps
  // ...省略

  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 标记这是个 React Element
    $$typeof: REACT_ELEMENT_TYPE,

    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };

  return element;
};
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

可看到,React.createElement最终会调用ReactElement方法返回一个包含组件数据的对象。 该对象有个参数$$typeof: REACT_ELEMENT_TYPE标记了该对象是个React Element。 所以调用React.createElement返回的对象就是React Element么?

React提供了验证合法React Element的全局API React.isValidElement (opens new window),看下它的实现:

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

所以,在React中,所有JSX在运行时的返回结果(即React.createElement()的返回值)都是React Element。

# React Component

在React中,常用ClassComponent与FunctionComponent构建组件。

class AppClass extends React.Component {
  render() {
    return <p>KaSong</p>
  }
}
console.log('这是ClassComponent:', AppClass);
console.log('这是Element:', <AppClass/>);


function AppFunc() {
  return <p>KaSong</p>;
}
console.log('这是FunctionComponent:', AppFunc);
console.log('这是Element:', <AppFunc/>);
1
2
3
4
5
6
7
8
9
10
11
12
13
14

从控制台可看到,ClassComponent对应的Element的type字段为AppClass自身。 FunctionComponent对应的Element的type字段为AppFunc自身:

{
  $$typeof: Symbol(react.element),
  key: null,
  props: {},
  ref: null,
  type: ƒ AppFunc(),
  _owner: null,
  _store: {validated: false},
  _self: null,
  _source: null 
}
1
2
3
4
5
6
7
8
9
10
11

值得注意的一点,由于

AppClass instanceof Function === true;
AppFunc instanceof Function === true;
1
2

所以无法通过引用类型区分ClassComponent和FunctionComponent。React通过ClassComponent实例原型上的isReactComponent变量判断是否是ClassComponent。

ClassComponent.prototype.isReactComponent = {};
1

# JSX与Fiber节点

JSX是一种描述当前组件内容的数据结构,他不包含组件schedule、reconcile、render所需的相关信息。 如不包含如下信息(这些内容都包含在Fiber节点中):

  • 组件在更新中的优先级
  • 组件的state
  • 组件被打上的用于Renderer的标记

所以,在组件mount时,Reconciler根据JSX描述的组件内容生成组件对应的Fiber节点。

在update时,Reconciler将JSX与Fiber节点保存的数据对比,生成组件对应的Fiber节点,并根据对比结果为Fiber节点打上标记。

#前端#React技术揭秘
上次更新: 2022/09/16, 17:10:42
Fiber
render阶段

← Fiber render阶段→

最近更新
01
面试官
03-27
02
this&指针&作用域&闭包
03-27
03
前端
03-27
更多文章>
Theme by Vdoing | Copyright © 2019-2025 chst365 | 豫ICP备17031889号-1
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式