本文主要通过手写一个简单的 React,旨在了解 Facebook 团队使用两年多时间重构的 Fiber 架构到底做了些什么?从而对 React 基本原理有一个直观的认识。尬不多说,搭建开始~
青铜 – React、JSX、DOM elements 如何工作的?
本文主要基本 React 16.8 版本进行实现。
下面先实现一个最简单的页面渲染,快速了解 JSX、React、DOM 元素的联系。
import React from "react";
import ReactDOM from "react-dom";
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
const container = document.getElementById("root");
ReactDOM.render(element, container);
实现一个最简单的 React 应用,只需要上面的三行代码就够了 👆,下面我们也将拆分三步进行分析,
- 创建 React 元素(React Element)
- 获取根节点 root
- 将 React 元素渲染到页面上
1. JSX 是如何被解析的 - Babel
const element = (
<div id="foo">
<a>bar</a>
<b />
</div>
);
用 JSX 创建了一个 react 元素,它不是有效的 JS,其实它是被 babel 解析为如下代码:
"use strict";
const element = /*#__PURE__*/ React.createElement(
"div",
{
id: "foo",
},
/*#__PURE__*/ React.createElement("a", null, "bar"),
/*#__PURE__*/ React.createElement("b", null)
);
可以看到 Babel 会将 JSX 转换成 React.createElement()
方法,其中 createElement()
方法接收三个参数,分别是元素类型 type
、元素属性 props
、和子元素 children
,后面我们会实现这个方法。参考 前端手写面试题详细解答
2. React 虚拟 DOM 对象的设计
React 的核心思想是在内存中维护一颗虚拟 DOM 树,当数据变化时更新虚拟 DOM,得到一颗新树,然后 Diff 新老虚拟 DOM 树,找到有变化的部分,得到一个 Change(Patch),将这个 Patch 加入队列,最终批量更新这些 Patch 到 DOM 中。
首先来看下基本的虚拟 DOM 结构:
const element = {
type: "div",
props: {
id: "foo",
children: [
{
type: "a",
props: {
children: ["bar"],
},
},
{
type: "b",
props: {
children: [],
},
},
],
},
};
可以看出 React.createElement() 方法其实就是返回了一个虚拟 DOM 对象。下面我们来实现 createElement()
这个方法,
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) =>
// 这里我们区分下基本类型和引用类型,用 createTextElement 来创建文本节点类型
typeof child === "object" ? child : createTextElement(child)
),
},
};
}
function createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
可以看到通过 Babel 编译后的 element 对象,其实是对 React.createElement()的递归调用所返回的数据结构 - 一个嵌套的虚拟 DOM 结构。
3. 实现 render() 方法
有了虚拟 DOM 结构,接下来需要根据它来生成真实节点并渲染到页面上,也就是 render() 方法的工作。基本分为以下四步:
- 创建不同类型节点
- 添加属性 props
- 遍历 children,递归调用 render
- 将生成的节点 append 到 root 根节点上
function render(element, container) {
// 1. 创建不同类型的DOM节点
const dom =
element.type == "TEXT_ELEMENT"
? document.createTextNode(<