学习建议:下载本节代码,对照着文章查看,尽量动手实现一遍。

1、前言

在React17之前,我们写React代码的时候都会去引入React,不引入代码就会报错,而且自己的代码中没有用到React,这是为什么呢?带着这个问题我们向下学习;

  1. import React from 'react'

2、element变量解析

我们先创建一个element变量,将本段代码放到babel上查看编译结果:

  1. const element = <h1 title="foo">Hello</h1>

通过babel会编译成下面这种形式:
image.png

经过编译后的代码为:

  1. const element = React.createElement("div", {
  2. title: "foo"
  3. }, "Hello");

element参数说明:

  • dom元素
  • 属性
  • children子元素

解答一下开篇提出的问题:引入React的作用,使用React进行解析JSX,如果不引入React,上面代码就会报错。JSX实际上是一个语法糖,它真正是需要解析成js代码来执行;

3、创建项目

我们先来创建执行命令:

  1. npm init

安装相关的依赖:

  1. npm install --save-dev babel-loader @babel/core
  2. npm install webpack --save-dev
  3. npm install --save-dev @babel/preset-react
  4. npm install --save-dev html-webpack-plugin

创建项目目录:
image.png

配置webpack:

  1. const HtmlWebpackPlugin = require('html-webpack-plugin');
  2. module.exports = {
  3. entry: {
  4. main: './src/index.js'
  5. },
  6. devServer: {
  7. port: 9000,
  8. },
  9. module: {
  10. rules: [
  11. {
  12. test: /\.js$/,
  13. use: {
  14. loader: 'babel-loader',
  15. options: {
  16. presets: ['@babel/preset-env'],
  17. plugins: ['@babel/plugin-transform-react-jsx']
  18. }
  19. }
  20. }
  21. ]
  22. },
  23. mode: "development",
  24. optimization: {
  25. minimize: false
  26. },
  27. plugins: [
  28. new HtmlWebpackPlugin({
  29. title: 'React',
  30. }),
  31. ],
  32. }

加入启动命令:
image.png

4、打印结果值

创建一个真实的React项目,使用create-react-app,本文就不在叙述安装过程。再来看看上文的 React.createElement 实际生成了的是什么?打印一下element:

  1. import React from 'react';
  2. import ReactDOM from 'react-dom';
  3. const element = <h1 title="foo">Hello</h1>
  4. console.log(element)
  5. const container = document.getElementById("root")
  6. ReactDOM.render(element, container)

打印结果:
image.png

简化一下,将其他属性刨除(其他属性我们不关心):

  1. const element = {
  2. type: "h1",
  3. props: {
  4. title: "foo",
  5. children: "Hello",
  6. },
  7. }

简单总结一下,React.createElement 实际上是生成了一个 element 对象,包含两个属性对象 type 和 props ,该对象拥有以下属性:

element对象参数:

  • type:标签名称
  • props:属性
    • title:标签属性
    • children:子属性

5、render简单流程

提前了解一下render的简单流程:
image.png

ReactDOM.render() 将 element 添加到 id 为 root 的 DOM 节点中,我们接下来实现这个方法来代替React源码中的 ReactDOM.render()方法;

示例代码:

  1. const element = {
  2. type: "h1",
  3. props: {
  4. title: "foo",
  5. children: "Hello",
  6. },
  7. }

1.首先,我们使用元素类型创建一个节点(element.type) ,在本例中是 h1;

  1. const node = document.createElement(element.type)

2.设置节点属性为title;

  1. node["title"] = element.props.title

3.只有一个字符串作为子节点,我们创建一个文本节点,并且设置文本节点的nodeValue为element.props.children;

  1. const text = document.createTextNode("")
  2. text["nodeValue"] = element.props.children

4.最后,我们将 textNode 附加到 h1,并将 h1附加到容器;

  1. node.appendChild(text)
  2. container.appendChild(node)

6、createElement实现(虚拟DOM)

用我们自己的代码实现React的代码;

从上文了解到createElement的作用是创建一个element对象:

  1. const element = {
  2. type: "h1", //标签
  3. props: {
  4. title: "foo", // 属性
  5. children: "Hello", // 节点
  6. },
  7. }

调用方式:

  1. const element = React.createElement("div", {
  2. title: "foo"
  3. }, "Hello");

根据调用和返回结果,设计createElement函数如下:

  1. // react/createElement.js
  2. /**
  3. * 创建虚拟DOM结构
  4. * @param {*} type 标签
  5. * @param {*} props 属性
  6. * @param {...any} children 自己诶单
  7. * @returns 虚拟DOM结构
  8. */
  9. export function createElement(type, props, ...children) {
  10. return {
  11. type,
  12. props: {
  13. ...props,
  14. children: children.map(child =>
  15. typeof child === "object"
  16. ? child
  17. : createTextElement(child) //不是对象说明是文本节点
  18. ),
  19. },
  20. }
  21. }
  22. /**
  23. * 当children为非对象时,创建文本节点
  24. * @param {*} text 文本值
  25. * @returns 虚拟DOM结构
  26. */
  27. function createTextElement(text) {
  28. return {
  29. type: "TEXT_ELEMENT",
  30. props: {
  31. nodeValue: text,
  32. children: [],
  33. },
  34. }
  35. }

为了直观的展示,我们更改一下element结构:

  1. const element = (
  2. <div id="foo">
  3. <a>bar</a>
  4. <b />
  5. </div>
  6. )

测试一下:

  1. // src/index.js
  2. import React from '../react';
  3. const element = (
  4. <div id="foo">
  5. <a>bar</a>
  6. <b />
  7. </div>
  8. )
  9. console.log(element

打印结果:
image.png

7、本节代码

地址:https://github.com/linhexs/mini-react/tree/1.createElement