react源码之createElement(step 1)

「这是我参与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;
复制代码

参考:

猜你喜欢

转载自juejin.im/post/7068648612216963085
今日推荐