编写简易的 react 框架
目的
- 性能优化(例如:调度的优先级)
- 学习优秀的代码(设计理念、架构、协同规范、代码编写)
- 解决开发中遇到的疑难杂症,更好的去思考遇到问题
学习构建简易 React 几个学习阶段
- 介绍 createElement 与 render
- 实现 createElement
- 实现 render
- 介绍并发模式
- 实现 Fibers
- render 和 commit 阶段
- 实现协调
- 支持函数组件
- 实现 Hooks
阶段一(介绍 createElement 与 render)
热身回顾基本概念
我们先来回顾下一些基本概念,React、JSX,DOM 元素
const element = <h1 title="foo">Hello</h1>;
const container = document.getElementById("root");
ReactDOM.render(element, container);
以这个仅有三行的 React App 为例,第一行定义了个 React element,第二行从 DOM 中拿到了一个节点,最后一行将 React element 渲染到容器中。
第一行我们定义了 JSX 元素。这不是合法的 JavaScript 代码,因此我们需要将其替换成合法的 JavaScript 代码。
JSX 通过构建工具 Babel 转换成 JS。这个转换过程很简单:将标签中的代码替换成 createElement,并把标签名、参数和子节点作为参数传入。
createElement
🔈:思路整理分为两部分,先从 js 对象生成 dom 结构去出发,然后用 babel 去处理 jsx 语法
(一) 用 js 对象生成 dom 结构
- 根DOM节点(为下文做铺垫)
<body>
<div id="root"></div>
</body>
- 先看下面这一则咱们编写的 js 对象,来让它实现生成 dom 结构
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
};
- 生成 dom 结构
HTML元素通常是由元素节点和文本节点组成,创建一个标题 (h1), 你必须创建 "h1" 元素和文本节点。
// Step1: 获取根节点用于最后挂载
const container = document.getElementById("root");
// Step2.利用上述 js 对象创建DOM元素,最终会生成 <h1></h1>
const node = document.createElement(element.type);
node['title'] = element.props.title;
// Step3.创建一个文本节点
const text = document.createTextNode("");
text['nodeValue'] = element.props.children; // nodeValue 属性返回或设置当前节点的值
// Step4: 创建出来的DOM节点,添加文本节点
node.appendChild(text);
// Step5: (done) 最后添加到在到根DOM节点 root 上
container.appendChild(node);
- 最终呈现的效果
<body>
<div id="root">
<h1 title="foo">Hello</h1>
</div>
</body>
“
如果你对上述的代码看不懂,不能马上理解,说明你对 javascript 三大组成部分 DOM 篇 还不太熟悉!!!
那就继续打卡对自己的技术盲区查漏补缺吧 ⇨ 浅入了解 DOM 篇
”
(二) 用 babel 处理 jsx 语法
Step1: 先编写一则 JSX 代码
const element = <h1 title="foo">Hello</h1>
const container = document.getElementById("root")
ReactDOM.render(element, container)
第一行我们定义了 JSX 元素。这不是合法的 JavaScript 代码,因此我们需要将其替换成合法的 JavaScript 代码。
分析:JSX 通过 babel 转换成 js对象结构上有两种情况
- 情况一
JSX 通过构建工具 Babel 转换成 JS。这个转换过程很简单:将标签中的代码替换成 createElement,并把标签名、参数和子节点作为参数传入。React.createElement 验证入参并生成了一个对象。因此我们将其替换成如下代码。
/**
* React.createElement
* @param {type} string
* @param {props} object
*/
const element = React.createElement(
"h1",
{ title: "foo" },
"Hello"
)
const container = document.getElementById("root")
ReactDOM.render(element, container)
一个 JSX 元素就是一个带有 type 和 props 属性的对象(其实还有更多属性),但是我们这里只关心这两个属性。
type 属性是个字符串,指定了 DOM 节点的类型。type 就是JSX中的标签名,最终会当做 tagName 传给 document.createElement 创建 HTML 元素。它也可以是一个函数。
props 也是个对象,从 JSX 属性中接收所有 key、value,并且有一个特别的属性 children。
- 情况二
在下面这个例子中 children 是一个字符串,但是在一般情况下,他会是一个由元素组成的数组。由此 element 的结构形成了一个树。
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
}
const container = document.getElementById("root")
ReactDOM.render(element, container)
我们需要替换的另一部分 React 代码是 ReactDOM.render。
React 在 render 函数里会处理改变 DOM,先不了解render内部实现,利用上述示例 js 对象我们先手动更新下DOM 。
const node = document.createElement(element.type)
node["title"] = element.props.title
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
node.appendChild(text)
首先我们根据传入的 type 创建了一个 node ,在这个例子中是 'h1'
然后我们把 props 传递给 node,在这个例子中就是 ‘title’。
为了避免混淆,我会用 “element” 对象来代指React内部 JSX通过 Babel 转换过的 React.createElement 所需的对象结构
const element = {
type: "h1",
props: {
title: "foo",
children: "Hello",
},
}
const container = document.getElementById("root")
// 接下来创建 node 的子节点。在这个例子中,子节点是字符串,因此我们需要创建一个 text 节点。
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);
使用 textNode 而不是 innerText 有助于我们稍后统一处理所有的 element。和我们给 h1 的 title 进行赋值一样,我们对 textNode(文本节点) 的 nodeValue(DOM节点) 进行赋值,像这样:props: { nodeValue: "Hello" }
最后,我们把 textNode 添加到 h1 里,把 h1 添加到 container 里。
在不使用 React 的情况下,我们成功渲染了和 React 相同的内容, 理解利用结合 Babel 把 JSX 语法转换为 JS 对象再渲染到根节点上的概念就结束了!