联系作者
联系作者

# 【译】build-your-own-react

背景

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

如果您读过我之前的build your own React,不同之处在于这篇文章基于 React16.8,所以我们现在可以使用hooks并删除与class相关的所有代码。

你可以在 Didact 存储库中找到旧博客文章和代码的历史记录。还有一个演讲涵盖了相同的内容。但这是一个独立的帖子。

目录

Step0: Review(回顾)

我们先回顾一些基本概念,如果你对React、JSX、DOM元素的工作原理比较了解,你可以跳过这一步。

我们将使用以下三行代码的定义一个React App。

const element = <h1 title="foo">Hello</h1> // 定义一个react元素
const container = document.getElementById("root") // 从DOM 中获取了一个 DOM node
ReactDOM.render(element, container) // 将 React 元素渲染到容器中
1
2
3

下一步我们将react代码替换成普通的javascript代码。

在第一行中,我们用JSX定义的React元素,这不是合规的js代码,因此我们要将其转换成合规的js代码。

JSX 通过Babel等构建工具转换为JS。转换过程通常很简单:将标签(tags)代码替换成createElement的调用,并将标签(tag)名、props 和 children 作为参数传入。

const element = React.createElement(
  "h1",
  { title: "foo" },
  "Hello"
)
1
2
3
4
5

React.createElement 根据参数创建对象。除了一些验证之外,这就是它所做的全部工作。因此,我们可以放心地将函数调用替换为其输出。

const element = {
  type: "h1",
  props: {
    title: "foo",
    children: "Hello",
  },
}
1
2
3
4
5
6
7

以上就是一个React Element ,一个具有两个属性(type与props)的对象。(好吧,它还有更多,但我们只关心这两个)。

这个type是一个字符串,它指定了我们要创建DOM node节点的类型,tagName是你想要通过document.createElement创建HTML元素时传递给它的字符串;它也可以是个函数,但我们将它留给Step7

props是另一个对象,它具有JSX属性中的所有键和值。它还具有特殊属性:children。

children在本例中是一个字符串,但它通常是一个包含更多elements的数组。这就是为什么elements也是树。

我们需要替换的另一段React代码:ReactDOM.render。

render是 React 更改 DOM 的地方,所以让我们手动实现dom的更新。

首先我们使用element type属性创建了一个 DOM node ,在这个例子中是h1 。

然后我们将所有 element props 分配给这个DOM node,在这里是只有一个 title。

const node = document.createElement(element.type)
node["title"] = element.props.title
1
2

TIP

为了避免混淆,我将使用 “element” 来指代 React元素,使用 “node” 来指代 DOM 元素。

然后,我们为子节点创建节点。我们只有一个字符串作为子节点,所以我们创建了一个文本节点。

const text = document.createTextNode("")
text["nodeValue"] = element.props.children
1
2

使用textNode而不是设置innerText,接下来我们以后以相同的方式处理。还要注意我们如何设置nodeValue标题,这个过程与给 h1 设置 title props类似,这就像是字符串中带有这样一个 props: {nodeValue: "hello"}。

最后,我们将textNode 添加至 h1,将h1添加至 container 。

现在我们有了和以前一样的app,但没有使用React。

const element = {
  type: "h1",
  props: {
    title: "foo",
    children: "Hello",
  },
}const container = document.getElementById("root")const node = document.createElement(element.type)
node["title"] = element.props.title
​
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
​
node.appendChild(text)
container.appendChild(node)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

Step1: The createElement Function(createElement 函数)

让我们从另一个app重新开始。这一次,我们将用我们自己的React版本替换官方React代码。

我们将编写自己的 createElement。

让我们将 JSX 转换为 JS,这样我们就能实现 createElement 函数的调用了

正如我们在上一步中看到的,element是带有 type 和 props的对象。我们的函数唯一需要做的就是创建该对象。

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

我们使用扩展运算符处理props,children使用rest参数语法,这样children将始终是一个数组。

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children,
    },
  }
}
1
2
3
4
5
6
7
8
9

举例:

  • createElement("div") 返回:
{
  "type": "div",
  "props": { "children": [] }
}
1
2
3
4
  • createElement("div", null, a)
{
  "type": "div",
  "props": { "children": [a] }
}
1
2
3
4
  • createElement("div", null, a, b)
{
  "type": "div",
  "props": { "children": [a, b] }
}
1
2
3
4

数组children还可以包含字符串或数字等原始值。因此我们为所有不是对象的内容创建一个独立的元素,并为其创建一个特殊的类型: TEXT_ELEMENT 。

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

function createTextElement(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#

Step2:render Function

#

Step3: Concurrent Mode (并行模式)

#

Step4: Fibers

#

Step5: Render 和 Commit 阶段

#

Step6: Reconciliation (协调算法)

#

Step7: Function Components 函数组件

Step8: Hooks

最新更新时间: 1/14/2024, 1:54:31 PM