概要
- 本文是作者在读懂了大神
Rodrigo Pombo
的《Build your own React》源码后,加上了自己的理解,以及做了少量修改后的实现,在这里再次感谢大神!🙏🏻 - 核心代码200+ 🎉
- fiber架构的react 🔥
- 通俗易懂,对标全网最简单的react实现 😍
- 构建工具选用parcel,号称零配置
- 从零到一的实现一个react
- 每篇文章在最后,都会附上当前章节源码 🌐
- github源码
创建一个空项目
创建项目并初始化package.json
mkdir Didact
npm init -y
安装 parcel
npm i -D parcel-bundler@^1.12.5
为了跟着教程走不会因版本问题报错,本文接下来的所有npm依赖都将带上版本号
新增 index.html 模板
添加 html 代码:
- 根节点
root
- 内联的方式引入 一个
index.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<title>Didact</title>
</head>
<body>
<div id="root"></div>
</body>
<script src="./index.js"></script>
</html>
添加入口 index.js
index.js
里面是一段 JSX 代码
console.log('成功启动');
增加 script 命令
修改 package.json
"scripts": {
+ "start": "parcel index.html",
},
执行 npm start
,访问http://localhost:1234/
, 会看到控制台打印"成功启动"
如何处理 JSX
什么是 jsx?
如下就是一段 JSX 代码, 具体请参考
const profile = (
<div className="profile">
<span className="profile-title">title</span>
<h3 className="profile-content">content</h3>
我是一段文本
</div>
);
接下来要将上面 JSX
代码转化为下面的数据结构
来描述
// 对象来描述jsx
const profile = {
type: "div",
props: {
className: "profile",
children: [
{type: 'span', props: {…}},
{type: 'h3', props: {…}},
"我是一段文本"
],
},
}
安装 babel 处理 JSX
我们使用@babel/preset-react
来转化 jsx,但同时需安装它所需要依赖——babel-core
,所以整体安装命令如下:
npm i -D @babel/preset-react@^7.17.12 babel-core@^7.0.0-bridge.0
然后再根目录添加.babelrc
文件:
{
"presets": [
[
"@babel/preset-react",
{
// 这样写,babel会调用 Didact.createElement函数 来递归生成 jsx对象
"pragma": "Didact.createElement"
}
]
]
}
注意:我们在上面设置了pragma
属性,它指定了babel
通过调用Didact.createElement
来递归JSX,从而生成上面的数据结构。
测试 JSX 的转换
将 index.js 的console打印改为上面的那段 JSX
- console.log('成功启动');
+ const profile = (
+ <div className="profile">
+ <span className="profile-title">title</span>
+ <h3 className="profile-content">content</h3>
+ 我是一段文本
+ </div>
+ );
+ console.log('profile: ', profile);
打开控制台,会看到如下错误提示:
Uncaught ReferenceError: Didact is not defined
at Object.parcelRequire.index.js (index.js:29:3)
at newRequire (Didact.e31bb0bc.js:47:24)
at Didact.e31bb0bc.js:81:7
at Didact.e31bb0bc.js:120:3
点击进入第一行错误定位,会跳转到源码出错的地方—— “Didact.createElement 未定义”,因为我们还未实现Didact.createElement
,所以因找不到该函数而报错。
但在实现createElement
方法前,我们先看看babel是如何处理jsx的。
babel转换jsx的过程
babel调用Didact.createElement
转换jsx的过程如下:
var profile = Didact.createElement(
// HTML标签的类型
"div",
// 该HTML标签的属性
{ className: "profile" },
// 后面都该HTML标签的children
// 第一个child
Didact.createElement(
"span",
{ className: "profile-title" },
"title"
),
// 第二个child
Didact.createElement(
"h3",
{ className: "profile-content" },
"content"
),
// 第三个child
"我是一段文本"
);
console.log('profile: ', profile);
// ...
从上面代码可以看出,@babel/preset-react
做了两件事情:
- 将 JSX 代码转换成了参数,
type, props, ...children
- 将上面的参数传递给 Didact.createElement,并执行该函数
Didact.createElement(
type,
[props],
[...children]
)
// 参数说明:
// - type:标签类型,如:`div`、`span`、`h3`,
// 也可以是 React 组件 类型(class 组件或函数组件)
// - props: 该标签的属性,如`classname`, 若无则为 null
// - children:第 2、3...个参数,都是子元素,子元素又开始递归调用`React.createElement`
实现 createElement 方法
很简单,让Didact.createElement
返回一个含有 children 的树状结构,就实现了createElement
在index.js中添加:
+ function createElement(type, props, ...children) {
+ return {
+ type,
+ props: {
+ ...props,
+ ...children,
+ }
+ };
+ }
+ const Didact = {
+ createElement,
+ };
const profile = (
<div className="profile">
<span className="profile-title">title</span>
<h3 className="profile-content">content</h3>
我是一段文本
</div>
);
console.log('profile: ', profile);
这样就实现了createElement
方法。
但通过console打印发现,children
中的所有元素,除了文本节点
是string
其它节点都是对象
。
这里将文本节点也统一处理成对象,这样后面会少了很多if、else
的判断。
将文本节点构建为type:'TEXT_ELEMENT'
的对象,修改代码:
function createElement(type, props, ...children) {
return {
type,
props: {
...props,
- ...children,
+ children: children.map(child =>
+ typeof child === "object" ? child : createTextElement(child)
+ )
}
};
}
+ function createTextElement(text) {
+ return {
+ type: "TEXT_ELEMENT",
+ props: {
+ nodeValue: text,
+ children: []
+ }
+ };
+ }
const Didact = {
createElement,
};
这样我们就彻底完成了createElement
方法