本文主要通过手写一个简单的 React,旨在了解 Facebook 团队使用两年多时间重构的 Fiber 架构到底做了些什么?从而对 React 基本原理有一个直观的认识。尬不多说,搭建开始~
本文主要基本 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 应用,只需要上面的三行代码就够了 👆,下面我们也将拆分三步进行分析,
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,后面我们会实现这个方法。参考 前端手写面试题详细解答
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 结构。
有了虚拟 DOM 结构,接下来需要根据它来生成真实节点并渲染到页面上,也就是 render() 方法的工作。基本分为以下四步:
function render(element, container) {
// 1. 创建不同类型的DOM节点
const dom =
element.type == "TEXT_ELEMENT"
? document.createTextNode(<