乐趣区

关于前端:源码向-14-用小白的视角构建react库

写在前边

  • 创作本篇博客的初衷是,在浏览社区时发现了 https://pomb.us/build-your-own-react/ 这篇宝藏文章,该博主基于 react16 之后的 fiber 架构实现了一套 react 的繁难版本,十分有助于了解 react 工作原理。然而苦于只有英文版本,且偏差实践。
  • 本着晋升自我、奉献社区的理念。在此记录下学习历程,并尽本人微薄之力对重点局部(联合本人了解)进行翻译整顿。心愿对大家有所帮忙。
  • 内容比拟多,所以篇幅会较扩散,大家也能够次要看本人须要的局部。我会致力更新的!

零、筹备工作

  1. 创立我的项目(本人命名),下载文件包

    $ mkdir xxx
    $ cd xxx
    $ yarn init -y / npm init -y
    $ yarn add react react-dom

  2. 建设如下目录构造

    • src/

      • myReact/
      • index.js
      • index.html
      • main.jsx
  3. 初始化文件内容

    //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 {}

  4. 装置 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/

退出移动版