React JSX原理与实现学习报告

1,190 阅读2分钟

最近在学习 “winter 手把手带你实现 ToyReact ”课程,这是第一节课的学习报告心得,记录一下学习历程

ToyReact顾名思义即实现一个玩具react,也就是一个极简react,通过这个极简react来了解react框架的内部原理

1.准备工作

需要提前安装好nodejs和npm环境,然后npm init -y然后安装"webpack"、"webpack-cli"、"@babel/core(把高级别es版本翻译成es5)"、"@babel/plugin-transform-react-jsx(将html结构转成jsx格式)"、 "@babel/preset-env"

2.代码编写

新建一个目录在目录中分别新建webpack.config.js toy-react.js main.js

webpack.config.js

@babel/plugin-transform-react-jsx做了什么呢? 如在js代码中写了一个这样的函数

输入

const profile = (
  <div>
    <img src="avatar.png" className="profile" />
    <h3>{[user.firstName, user.lastName].join(' ')}</h3>
  </div>
);

输出

const profile = React.createElement("div", null,
  React.createElement("img", { src: "avatar.png", className: "profile" }),
  React.createElement("h3", null, [user.firstName, user.lastName].join(" "))
);

如果需要将React.createElement自定义可以将pragma参数改成如:'createElement'这样的话上面的输出会变成

const profile = createElement("div", null,
  createElement("img", { src: "avatar.png", className: "profile" }),
  createElement("h3", null, [user.firstName, user.lastName].join(" "))
);

webpack配置如下:

module.exports = {
    entry: {
        main: './main.js'
    },
    module: {
        rules: [
            {
                test:/\.js$/,
                use: {
                    loader: 'babel-loader',
                    options: {
                        presets: ['@babel/preset-env'],
                        plugins: [['@babel/plugin-transform-react-jsx',{pragma: 'createElement'}]]
                    }
                }
            }
        ]
    },
    mode: 'development',
    optimization: {
        minimize: false
    }
}

main.js

import { createElement, Component, render } from './toy-react'
class MyComponent extends Component {
    render(){
        return <div>
            <p>MyComponent</p>
            {this.children}
        </div>
    }
}

render(<MyComponent id="a" class="b" >
    <div>abc</div>
    <div>2</div>
</MyComponent>, document.body)

我们发现main.js在@babel/plugin-transform-react-jsx的翻译下,代码实际上会变成如下模样,@babel/plugin-transform-react-jsx会把首字母大写的自定义元素转成一个构造函数(类)如果是一个类,如在代码中创建一对元素,即我们需要实例化MyComponent并给实例化后的实例对象设置属性id和class,如果是普通元素如

我们直接使用dom自带的api 如果是元素节点则使用document.createElement(),如果是文本节点则使用document.createTextNode() 设置属性使用ele.setAttribute(name, value)

下面的createElement执行顺序如下面数字序号

import { createElement, Component, render } from './toy-react'
class MyComponent extends Component {
    render(){
        return createElement(//第5步
            "div",
            null,
            createElement("p", null, "MyComponent"),//第4步
            this.children
        )
    }
}

render(
    createElement(//第3步
      MyComponent,
      {
        id: "a",
        class: "b",
      },
      createElement("div", null, "abc"),//第1步
      createElement("div", null, "2")//第2步
    ),
    document.body
)

toy-react.js

实现toy-react 需要 元素节点ElementWrapper 文本节点TextWrapper Component createElement render 这些函数或类

// 元素节点
class ElementWrapper{
    constructor(type) {
        this.root = document.createElement(type)
    }
    setAttribute(name, value) {
        this.root.setAttribute(name, value)
    }
    appendChild(component) {
        this.root.appendChild(component.root)
    }
}
// 文本节点
class TextWrapper{
    constructor(content){
        this.root = document.createTextNode(content)
    }
}
// 组件
export class Component{
    constructor(){
        this.props = Object.create(null)//创建一个无_proto_的对象
        this.children = []
        this._root = null
    }
    setAttribute(name, value) {
        this.props[name] = value
    }
    appendChild(component) {
        this.children.push(component)
    }
    get root(){
        if(!this._root) {
            this._root = this.render().root
        } 
        return this._root
    }
}
// 文本节点
export  function createElement(type, attributes, ...children) {
    let e ;
    if(typeof type === 'string') {
        e = new ElementWrapper(type)//document.createElement(type)
    } else {
        e = new type
    }
    for(let p in attributes) {
        e.setAttribute(p, attributes[p])
    }
    let insertChildren = function(children) {
        for(let child of children){
            if(typeof child === 'string'){
                child = new TextWrapper(child)//document.createTextNode(child)
            }
            if(typeof child === 'object' && child instanceof Array){
                insertChildren(child)
            } else {
                e.appendChild(child)
            }

        }
    }
    insertChildren(children)
    return e
}
export function render(component, parentElement) {
    parentElement.appendChild(component.root)
}

如果需要查看完整代码请查看我的github

github.com/PH-C/toy-re…