菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
52
0

不到一百行代码,我们来实现一个简简简简简简简简简简版react库

原创
05/13 14:22
阅读数 1092

good evening everybody!这是一篇关于react故事的文章,这个故事主要是讲在一个夜黑风高晚上,react从一个VDOM变成真实DOM的过程。

这个过程react经历了从JSX->React.createElement->VDOM->ReactDom.render->真实DOM的大体一个流程。

使用过react的开发者都知道,react一开编写的就是JSX。

以上这段看上去是HTML,但其实它是JSX。什么是JSX?为什么用JSX?它有什么好?

  • JSX是对JS的一个拓展。JSX将HTMl语法直接加入到JS代码中,再通过编译器(Babel-loader)转换成纯JS给浏览器执行。
  • JSX只要用于构建React的View层,类HTML语法,学习成本几乎为零。
  • JSX相对于直接用React.createElement更快,更容易维护,更简洁。

卧槽,怎么说到了JSX里面去了,成吉思汗你真不是人,当大佬们什么都不懂啊。

React.createElement又是什么?它能做什么?又是怎样做的???

  • React.createElement是react里面提供的一个核心方法,它可以将JSX转换成VDMO;说是核心方法,但这个放法实现非常简单,就是返回了一个JS对象。

ReactDom.render又是什么?它能做什么?又是怎样做的???

  • ReactDom.render是ReactDom提供的一个核心方法;ReactDom是FB团队故意从react中抽离出来的,但它只能渲染在浏览器。ReactDom.render可以将React.createElement转换成的VDOM,变成真实DOM挂载在浏览器上。

好了,react从VDOM到真实DOM的转换流程概念大概就是这样,现在通过手写React.createElement,React.Component,ReactDom.render三个API(这里实现的三个API,只是负责这个流程的实现,不带任何属性(class,key,ref...)的处理,和diff算法(tree diff,component diff,element diff)之类的东西,是三个极简版的API)

首先,我们通过create-react-app创建一个工程,然后将其他文件删掉,只留下index.js

在src文件在创建一个文件夹(custom)存放自己实现的react(customReact),和react-dom(coutomReactDom)。

index.js

import React from './custom/customReact';
import ReactDOM from './custom/customReactDom';

let arr = ['su', 'yue', 'feng']

function Container(props) {
    return <div>{props.name}</div>
}
class Test extends React.Component {
    constructor(props) {
        super(props)
    }
    render() {
        return <div>
            {this.props.name}
            <ul>
                {
                    arr.map(item => <li key={item}>{item}</li>)
                }
            </ul>
        </div>
    }
}
const JSX = (
    <div>
        <Container name="函数组件" />
        <span>普通文本</span>
        <Test name="类组件" />
    </div>
)

ReactDOM.render(JSX, document.getElementById('root'));

以上index.js文件我们写上两个组件,(一个函数组件也叫无状态组件,之从hook出来之后,就不能再叫无状态组件了;一个类组件),还有一些普通标签组件。构成reactVDOM无非就这几种类型。

当我们打包编译的时候,以上的JSX就会被React.createElement转换成VDOM;所以我们在customReact中起码要实现一个createElement方法;如果使用了类组件,还有实现一个Component方法。

function createElement(type, props, ...children) {

    let vtype

    props.children = children

    if (typeof type === 'string') {
        vtype = 1
    } else if (typeof type === 'function') {
        if (type.prototype.render) {
            vtype = 2
        } else {
            vtype = 3
        }
    }
    return { vtype, type, props }
}

class Component {

    constructor(props) {
        this.props = props
        this.state = {}
        this.refs = {}
    }
    setState() { }
    
    forceUpdate(){}
}

export default { createElement, Component }

createElement方法接受三个参数,type(标签类型),prop(属性),children(子标签,子属性,这有可能是多个,所以通过es6展开符...,将它们搞成一个数组)。

在createElement里面我们通过type的类型判断他是普通标签,还是组件。如果type是string类型的话,就是普通标签,我们用1表示;如果type是function类型的话,就是组件,但是组件又分函数组件,和类组件,这两种组件,我们用render方法区分,如果又render方法的就是类组件,我们用2表示,没有render方法的就是函数组件,我们用3表示。最后返回一个JS对象。

在Component方法里面,我们什么也不做就是初始换一个变量,setState(),forceUpdate()这两个方法应该大家都不陌生。这里就不去实现,因为这部分的内容太多,涉及到diff,执行机制,更新机制等等,有兴趣的自己去看源码。

最后我们导出这两个方法。

在打包编译的时候,当JSX就会被React.createElement转换成VDOM后,就会执行,react-dom的render方法,这个放法会将VDMO变成真实DOM,所以我们在实现自己的react-dom的时候起码要有一个render方法。

coutomReactDom.js

function render(vnode, container) {
    container.appendChild(initVnode(vnode))
}

function initVnode(vnode) {
    if (!vnode.vtype) {
        return document.createTextNode(vnode)
    }
    if (vnode.vtype === 1) {
        return createElement(vnode)
    } else if (vnode.vtype === 2) {
        return createClassCom(vnode)
    } else if (vnode.vtype === 3) {
        return createFunctionCom(vnode)
    }
}

function createElement(vnode) {
    let { type, props: { key, children } } = vnode

    let node = document.createElement(type)
    
    children.forEach(element => {
        if (Array.isArray(element)) {
            element.forEach(c => {
                node.appendChild(initVnode(c))
            })
        } else {
            node.appendChild(initVnode(element))
        }
    });

    return node
}

function createClassCom(vnode) {
    let { type, props } = vnode
    let instance = new type(props)
    return initVnode(instance.render())
}

function createFunctionCom(vnode) {
    let { type, props } = vnode
    return initVnode(type(props))
}

export default { render }

render方法接收两个参数,vnode(虚拟DOM),container(挂载节点)

在render里面,我们通过传进来的虚拟DOM的vtype(也就是我们刚才通过1,2,3,标记的类型)来创建真实的DOM。如果vnode没有vtype的话,我们就通过document.createTextNode(vnode)来创建该文本,如果是1的话,我们就通过createElement方法来创建,createElement方法,只要是通过document.createElement()和递归的方式实现创建节点的流程。如果是2的话,我们就通过createClassCom()方法去创建节点;如果是3的话就通过createFunctionCom()方法去创建节点。这两个方法及其简单加起来就五行代码,这两个方法只要是去拿到组件的虚拟节点,然后给initVnode方法就看可以。因为无论什么组件,到最后还是跑回到创建普通标签的流程,也就是最后还是回到createElement方法里面。

好了,这样就大功告成了,这样就实现了自己的一个react库,俩个文件加起来也只有70行代码。(真正的react里面的做的东西,处理的东西会比这个复杂上千万赔,这里只不过是实现是它从VDOM到真实DOM的转换大概流程)

这是跑起来的效果(npm start)

有不足的地方,希望大佬们指出来。
image.png

发表评论

0/200
52 点赞
0 评论
收藏
为你推荐 换一批