写在前边
- 创作本篇博客的初衷是,在浏览社区时发现了 https://pomb.us/build-your-own-react/ 这篇宝藏文章,该博主基于 react16 之后的 fiber 架构实现了一套 react 的繁难版本,十分有助于了解 react 工作原理。然而苦于只有英文版本,且偏差实践。
- 本着晋升自我、奉献社区的理念。在此记录下学习历程,并尽本人微薄之力对重点局部(联合本人了解)进行翻译整顿。心愿对大家有所帮忙。
- 内容比拟多,所以篇幅会较扩散,大家也能够次要看本人须要的局部。我会致力更新的!
零、筹备工作
- 创立我的项目(本人命名),下载文件包
$ mkdir xxx
$ cd xxx
$ yarn init -y / npm init -y
$ yarn add react react-dom -
建设如下目录构造
-
src/
- myReact/
- index.js
- index.html
- main.jsx
-
- 初始化文件内容
//index.html
<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”utf-8″ />
<meta name=”viewport” content=”width=device-width, initial-scale=1″ />
<title>React App</title>
</head>
<body>
<div id=”root”></div>
<script src=”main.jsx”></script>
</body>
</html>
// main.jsx
import React from “./React/index”;
import React from “react”;
import ReactDom from “react-dom”;
const App = () => {
return <div title=”oliver”>Hello</div>;
};
ReactDom.render(<App />, document.getElementById(“root”));
// myReact/index.js
export default {} - 装置 parcel 用于打包和热更新
$ yarn add parcel-bundler
一、createElement 的性能
功不可没的 babel
// main.jsx
const element = (
<div id=”foo”>
Hello
<span />
</div>
)
通过 babel 转译后的成果(应用 plugin-transform-react-jsx
插件,https://www.babeljs.cn/docs/babel-plugin-transform-react-jsx#both-runtimes):
const element = React.createElement(
“div”, //type
{id: “foo”}, //config
React.createElement(“a”, null, “bar”), //…children
React.createElement(“span”)
)
- babel 的
plugin-transform-react-jsx
做的事件很简略:应用React.createElement
函数来从解决.jsx 文件中的 jsx 语法。 - 这也就是为什么在.jsx 文件中必须
import React from "react"
的起因啦,否则插件会找不到 React 对象的!
配置 babel
tips:笔者原本也打算应用 plugin-transform-react-jsx
插件,然而在调试中遇到了问题。查找后才晓得最新版本的插件曾经不再是由 <h1>Hello World</h1>
到 React.createElement('h1', null, 'Hello world')
的简略转换了(具体见 https://zh-hans.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html),故退而求其次抉择了性能相似的 transform-jsx
$ touch .babelrc
$ yarn add babel@transform-jsx
// .babelrc
{
“presets”: [“es2015”],
“plugins”: [
[
“transform-jsx”,
{
“function”: “OllyReact.createElement”,
“useVariables”: true
}
]
]
}
$ parcel src/index.html
此时页面中能够看到 Hello 字样,阐明咱们配置胜利了!
入手实现 createElement
transform-jsx
插件会将参数封装在一个对象中,传入 createElement。
// myReact/index.js
export function createElement(args) {
const {elementName, attributes, children} = args;
return {
type:elementName,
props: {
…attributes,
children
}
};
}
思考到 children 中还可能蕴含根本类型如 string,number。为了简化操作咱们将这样的 children 对立应用 TEXT_ELEMENT
包裹。
// myReact/index.js
export function createElement(type, config, …children) {
return {
type,
props: {
…attributes,
children: children.map((child) =>
typeof child === “object” ? child : createTextElement(child)
),
}
};
}
function createTextElement(text) {
return {
type: “TEXT_ELEMENT”,
props: {
nodeValue: text,
children: [],
},
}
}
export default {createElement}
React 并不会像此处这样解决根本类型节点,但咱们这里这样做:因为这样能够简化咱们的代码。毕竟这是一篇以性能而非细节为主的文章。
看看成果
首先为咱们本人的库起个名字吧!
//.babelrc
{
“presets”: [“es2015”],
“plugins”: [
[
“@babel/plugin-transform-react-jsx”,
{
“runtime”: “automatic”,
“importSource”: “OllyReact”
}
]
]
}
引入时就应用本人写的名字吧!
// main.jsx
import OllyReact from “./myReact/index”;
import ReactDom from “react-dom”
const element = (
<div style=”background: salmon”>
<h1>Hello World</h1>
<h2 style=”text-align:right”>—Oliver</h2>
</div>
);
ReactDom.render(element, document.getElementById(“root”));
此时页面上曾经呈现了Hello
,这证实咱们的 React.createElement 曾经根本实现了 React 的性能。
二、Render 性能
接下来编写 render 函数。
目前咱们只关注向 DOM 中增加内容。批改和删除性能将在后续增加。
// React/index.js
export function render(element, container) {}
export default {
//… 省略
render
};
细节实现
留神:
本大节每一步内容次要参考思路即可,具体的逻辑程序会在底部汇总。
- 首先应用对应的元素类型创立新 DOM 节点,并把该 DOM 节点退出股 container 中
const dom = document.createElement(element.type)
container.appendChild(dom) - 而后递归地为每个 child JSX 元素执行雷同的操作
element.props.children.forEach(child =>
render(child, dom)
) - 思考到 TEXT 节点须要非凡解决
const dom =
element.type == “TEXT_ELEMENT”
? document.createTextNode(“”)
: document.createElement(element.type) - 最初将元素的 props 调配给实在 DOM 节点
Object.keys(element.props)
.filter(key => key !== “children”) // children 属性要除去。
.forEach(name => {
dom[name] = element.props[name];
});
汇总:
export function render(element, container) {
const dom = element.type === “TEXT_ELEMENT”
? document.createTextNode(“”)
: document.createElement(element.type);
Object.keys(element.props)
.filter(key => key !== “children”)
.forEach(name => {
dom[name] = element.props[name];
});
element.props.children.forEach(child =>
render(child, dom)
);
container.appendChild(dom);
}
看看成果
// main.jsx
import OllyReact from “./myReact/index”;
const element = (
<div style=”background: salmon”>
<h1>Hello World</h1>
<h2 style=”text-align:right”>—Oliver</h2>
</div>
);
OllyReact.render(element, document.getElementById(“root”));
此时看到咱们的 render 函数也能够失常工作了!
小结
就是这样!当初,咱们有了一个能够将 JSX 出现到 DOM 的库(尽管它只反对原生 DOM 标签且不反对更新 QAQ)。
参考:https://pomb.us/build-your-own-react/