「这是我参与2022首次更文挑战的第26天,活动详情查看:2022首次更文挑战」
前言
从今天开始讲react系列。 react想必大家都用了很久了,可是react源码可能大部分同学还没有写过,主要的痛点就是找不到入手的地方,不知道该如何下手。我写这篇文章的目的也是希望能帮助想探索react源码,但是又无从下手的同学一些帮助,一起来探索react源码
准备环境
首先搭建一个简单的webpack环境
要安装的包
yarn add @babel/core @babel/preset-react @babel/preset-env babel-loader webpack webpack-cli webpack-dev-server html-webpack-plugin clean-webpack-plugin --dev
复制代码
.babelrc
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
复制代码
jsx
我们在写react的UI的时候,都使用jsx语法,jsx是js的一种扩展
const element = `<h1 title="foo">Hello<span>React</span></h1>`; //jsx
复制代码
我们都知道react使用的virtual dom,那么jsx语法是如何转化为虚拟dom的呢?
虽然jsx是js的一种扩展,但是浏览器并不能识别jsx,需要将jsx转化为js代码
答案是由Babel转译为对createElement
方法的调用
const babel = require("@babel/core");
const element = `<h1 title="foo">Hello<span>React</span></h1>`;
// 使用@babel/preset-react预处理对字符串进行转义
const result = babel.transform(element, {
presets: ["@babel/preset-react"],
});
console.log(result.code);
//输出
/*#__PURE__*/
React.createElement(
"h1",
{
title: "foo"
},
"Hello",
/*#__PURE__*/
React.createElement("span", null, "React")
);
复制代码
虚拟dom
那现在我们可以写自己的MyReact了
从上面的转义结果我们可以看出,createElement应该有三个参数
type->元素类型->对应"h1"
props->元素上面的属性->对应对象{title: "foo"}
children->对应子元素->"Hello"和React.createElement("span", null, "React"),所以我们children可以写成扩展运算符的形式
复制代码
class React {
createElement(type, props, ...children) {
return {
type,
props: {
...props,
children,
},
};
}
}
module.exports = new React();
复制代码
const React = require("./MyReact/react");
const babel = require("@babel/core");
const element = `<h1 title="foo">Hello<span>React</span></h1>`;
const result = babel.transform(element, {
presets: ["@babel/preset-react"],
});
// TODO:所以为什么我们的jsx中没有使用React,也要引用react,原因就在这里
const virtualDom = eval(result.code);
console.log(virtualDom);
复制代码
输出
{
type: 'h1',
props: { title: 'foo', children: [ 'Hello', [Object] ] }
}
复制代码
这就是我们的虚拟dom,是不是感觉很简单。 为什么叫虚拟dom,因为他不是真实的dom,他只是描述dom的js对象
文本节点的处理
和文本节点相关,我们可以看出我们的virtualDOM的文本节点是以文本字符串的形式存在VirtualDOM当中的,这明显不符合我们的要求。我们的要求是即使是文本节点,也要以节点对象的形式表示出来。
class React {
createElement(type, props, ...children) {
return {
type,
props: {
...props,
children: children.map((child) =>
typeof child === "object" ? child : this.createTextElement(child)
),
},
};
}
createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
nodeValue: text,
children: [],
},
};
}
}
module.exports = new React();
复制代码
在JSX中有js表达式他的值呢是true
或者false
,根据我们之前学习了解到,virtualDOM中不展示true, flase ,null
这三个值的,我们应该将这三个值清除
优化
在讲第二节之前,我们先把我们现在写的东西优化一下,主要是要使用webpack进行编译,这样我们就不用每个jsx都要用babel转化,而是webpack会给我们完成这个操作,还有就是html不支持模块化(require,import),所以也需要使用webpack来进行打包
建立我们的webpack
const HtmlWebpackPlugin = require("html-webpack-plugin");
const path = require("path");
module.exports = {
mode: "development",
entry: path.join(__dirname, "/src/index.js"),
output: {
path: path.join(__dirname, "dist"),
filename: "bundle.[hash:8].js",
},
module: {
rules: [
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/,
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
}),
],
devServer: {
static: path.join(__dirname, "/dist"),
open: true,
},
};
复制代码
建立.babelrc
{
"presets": ["@babel/preset-env", "@babel/preset-react"]
}
复制代码
在src下创建index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="root"></div>
</body>
</html>
复制代码
在package.json中创建script
"scripts": {
"start": "webpack-dev-server --config webpack.config.js"
},
复制代码
我们的react代码现在为
class React {
createElement(type, props, ...children) {
return {
type,
props: {
...props,
// 有人可能要问,为什么要把children放到props中,这是因为,我们在写组件的时候,通常可以使用props.children拿到子组件,所以要把children放到props中
children: children.map((child) =>
typeof child === "object" ? child : this.createTextElement(child)
),
},
};
}
createTextElement(text) {
return {
type: "TEXT_ELEMENT",
props: {
// 有人可能说这里为什么叫nodeValue,我记不住呀,这是节点值的意思https://www.runoob.com/jsref/prop-node-nodevalue.html
nodeValue: text,
children: [],
},
};
}
}
export default new React();
复制代码
我们的jsx代码为
import React from "../MyReact/React";
const App = (
<h1 title="foo">
Hello<span style="color:blue">React</span>
</h1>
);
export default App;
复制代码