前情提要,也可以当作是一个小目录吧,我会在阅读完后把自己的理解做一个输出,励志让所有有 React 基本功的同学都能看懂。

我们将从头开始重写 React。一步步。遵循真实 React 代码的架构,但没有所有优化和非必要功能。

如果您阅读过我之前的任何一篇“构建自己的 React”文章,不同之处在于这篇文章基于 React 16.8,因此我们现在可以使用 hooks 并删除所有与类相关的代码。

您可以在Didact repo上找到包含旧博客文章和代码的历史记录。还有一个涵盖相同内容的演讲。但这是一个独立的帖子。

从头开始,这些是我们将一一添加到我们的 React 版本中的所有内容:

  • 第一步createElement功能
  • 第二步render函数
  • 第三步:并发模式
  • 第四步:纤维
  • 第五步:渲染和提交阶段
  • 第六步:和解
  • 第七步:功能组件
  • 第八步:钩子

from https://pomb.us/build-your-own-react/

Step I: The createElement Function

分析一下一个 React 元素转换成 DOM 的过程:程序员使用 React.createElement 并传入对应的 type 、props、children 参数后会生成一个 React Element,后由 ReactDOM 核心 API render 方法去将它转换成 DOM。知道了上述的生成逻辑就好办了。

第一个阶段:创建一个我们自己的 createElement 方法。

首先我们要了解 React 元素是如何转换成 DOM 节点的?我目前的理解其实 React 元素本质上就是一个对象。第一步就是创建一个类似于 React 元素的一个对象

用 JS 来创建一个 DOM元素

下一步我们要了解抛开 React 我们要如何用 JS 代码创建一个 DOM。

初始化一个 HTML 并声明一个 root 根节点来放页面的内容。

1
2
3
4
5
6
7
8
9
10
11
12
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>

获取 root 节点,用 DOM API 写入一个节点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 获得 root 根节点
const container = document.getElementById("root")

// 创建一个 h1 元素
const node = document.createElement("h1")
// 给节点一个属性 title
node["title"] = '这是一个 h1 的标题'

// 创建一个文本节点
const text = document.createTextNode("")
// 给文本节点中写入值
text["nodeValue"] = "1111"

// 将文本内容追加到 node 节点中
node.appendChild(text)
// 将 node 也就是 h1 追加到 root 中
container.appendChild(node)

了解 React.createElement 传递参数和执行后产出的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 这就是一个 React 元素的全貌了
const element = {
type: "h1", // type
props: { // props
title: "foo",
children: "Hello", // children
},
}

// 再对比一下 React.createElement 传入的参数来分析这个结构的生成逻辑
const element = React.createElement(
"h1",// type
{ title: "foo" }, // props
"Hello" // children
)

const element = React.createElement(
"div",// type
{ id: "foo" }, // props
React.createElement("a", null, "bar"), // children
React.createElement("b") // children
)

创建我们自己的 createElement func

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
// MyReact.js
// 用户传入的 children 可以是用 createElement 创建一个 MyReact 对象,也可以是一个数字或字符串,那么我们要帮用户将这些原始值转换成一个 Text 类型的 MyReact 对象
const createTextElement = text => {
return {
type: 'TEXT_ELEMENT',
props: {
nodeValue: text,
children: [],
},
};
};

const createElement = (type, props, ...children) => {
return {
type,
props: {
...props,
children: children.map(child => {
typeof child === 'object' ? child : createTextElement(child);
}),
},
};
};

const MyReact = {
createElement,
};

使用我们的 createElement 来创建 MyReact 元素

1
2
3
4
5
6
const element = MyReact.createElement(
"div",
{ id: "foo" },
MyReact.createElement("a", null, "bar"),
MyReact.createElement("b")
)

告诉 Babel 用我们指定的 createElement

但是我们仍然想在这里使用 JSX。我们如何告诉 babel 使用 MyReact 的createElement而不是 React 的?

1
2
3
4
5
6
7
8
9
/** @jsx MyReact.createElement */
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
)
const container = document.getElementById("root")
ReactDOM.render(element, container)