假期无聊,不如写写简单版React

各位小伙伴,春节假期已过大半,这个春节应该是历年来过的最安静的一个春节了吧,每天家里蹲,吃了睡,睡了吃,没想到不出门居然成了对社会最大的贡献。废话很少说,开始说正题。css

相信不少人都用过react开发项目,也有不少人好奇页面中明明没有直接用到React,可是页面却必须引入React,不然就会报错。初学者必定有这个疑问,不要着急,今天我就为你解答这个疑问。html

JSX

jsx是个啥?

jsx其实就是个语法糖,用写html的方式来写js,一个很像xml的js扩展。React使用jsx来替代常规的js。在线体验node

为何要引入jsx?

写js不香吗?为何要引入一个新概念jsx来迷惑你们?归纳来说jsx来说有如下几个好处:react

  • 提高开发效率。使用jsx来写模版速度嗖嗖的。
  • 提高执行效率。jsx编译为js代码后进行来不少优化,执⾏更快。
  • 类型安全。在编译过程当中就能发现错误。

尝试过上面的在线体验后,就会发现实际上babel-loader会把jsx预编译为React.createElement(xxx)。
jsx预处理前:webpack

jsx预处理后git

经过以上两张图片对比能够解答文章开头的疑问,为何React没有使用,却必须引入?其实并非没有使用,只不过没有直接使用,须要通过babel转化。另外能够看出React几个核心API:React.createElement, React.Component, ReactDom.render。

接下来咱们就本身动手来实现这三个API吧!github

核心API实现(简版)

createElement和Component

做用:将传入的节点转化成vdom。
(1)建立./simple-react/component.js。实现class组件必备条件。web

export class Component{
    static isReactComponent={};
    constructor(props){
        this.props=props;
        this.state={}
    }
}
复制代码

(2)建立./simple-react/index.js文件数组

import {Component} from "./component"

function createElement(type,props,...children){
    props.children=children;
    // console.log(type);
    // 判断组件类型
    let vtype;
    if(typeof type==="string"){
        // 原生标签
        vtype=1;
    }else if(typeof type === "function"){
        // 类组件,函数式组件
        vtype=type.isReactComponent ? 3 : 2;
    }
    return {
        vtype,
        type,
        props
    }
}

const React={
    createElement,
    Component
}

export default React;
复制代码

上面我只是简单使用了一、二、3来分别标示来标签类型,你也可使用别的方式来处理。createElement被调用时会传入标签类型type,标签属性props及若⼲子元素children。安全

render

做用:渲染vdom,挂载到真实dom树上。
建立./simple-react/ReactDOM.js文件,包含render函数。

function render(vnode,container){
    // vnode->node
    mount(vnode,container); // 待实现

}

const ReactDOM={
    render
}

export default ReactDOM;
复制代码

这样就实现了代码中的常见的ReactDOM.render(jsx,container)。接下来重点实现mount函数来处理vnode挂载。
建立./simple-react/virtual-dom.js文件。

export function mount(vnode,container){
    const {vtype}=vnode;
    if(!vtype){
        // 纯文本节点
        mountText(vnode,container);
    }
    if(vtype===1){
        // 原生节点
        mountHtml(vnode,container);
    }
    if(vtype===2){
        // 建立函数式节点
        mountFunc(vnode,container)
    }
    if(vtype===3){
        // 建立class类型组件
        mountClass(vnode,container);
    }
}

function mountText(vnode,container){
    let textNode=document.createTextNode(vnode);
    container.appendChild(textNode)
}

function mountHtml(vnode,container){
    const {type,props}=vnode;
    let htmlNode=document.createElement(type);
    const {children,...rest}=props;
    Object.keys(rest).map(item=> {
        if(item==="className"){
            htmlNode.setAttribute("class",rest[item]);
        }
        if(item.slice(0,2)==="on"){
            // 简单处理click事件,实际状况很复杂,须要考虑多种状况
            htmlNode.addEventListener("click",rest[item])
        }
    })
    children.map(item=>{
        if(Array.isArray(item)){
            item.map(i=>mount(i,htmlNode))
        }else{
            mount(item,htmlNode)
        }
    })
    container.appendChild(htmlNode)
}

function mountFunc(vnode,container){
    const {type,props}=vnode;
    let node=type(props);
    mount(node,container);
}

function mountClass(vnode,container){
    const {type,props}=vnode;
    let cmp=new type(props);
    let node=cmp.render();
    mount(node,container);
}
复制代码

mount函数建立好以后,完整的ReactDOM文件就能够改写为以下:

import {mount} from "./virtual-dom";

function render(vnode,container){
    // vnode->node
    mount(vnode,container)

}

const ReactDOM={
    render
}

export default ReactDOM;
复制代码

相关文件建立好以后,在由creat-react-app建立好的项目中改写index.js文件,引入本身写好的简版react文件,进行测试。

import React from "./simple-react";
import ReactDOM from "./simple-react/ReactDOM";
import './index.css'
function Funcomp(props){
    return (
        <div className="border">{props.name}</div>
    )
}

class ClassComp extends React.Component {
    constructor(props) {
        super(props);
        this.state = {  }
    }
    handleClick=()=>{
        alert("hello")
    }
    render() { 
        return (
            <div className="border">
                <h1>{this.props.name}</h1>
                {
                    [1,2,3].map(item=>
                    (
                        <h1 key={item}>{item}</h1>
                    )
                )
                }
                <button onClick={this.handleClick}>点我</button>
            </div>
        );
    }
}

let jsx=(
    <div>
        <div className="border">我是内容</div>
        <Funcomp name="我是函数组件内容" />
        <ClassComp name="我是class组件内容" />
    </div>
)
ReactDOM.render(jsx,document.getElementById("root"))
复制代码

实际页面效果若是和下面截图同样,表明着简版react大功告成。

总结

  • webpack+babel编译时,替换jsx为React.createElement(type,props,...children)。
  • 全部React.createElement()执⾏结束后获得⼀个JS对象即vdom,一个可以完整描述dom结构的对象。
  • ReactDOM.render(vdom,container)能够将vdom转换为真实dom并添加container中。

固然在实际react处理中,要处理的状况远比上面写的复杂多得多,dom的更新、替换、删除要通过diff的过程,打补丁,更新布丁等过程,v16.8以后的fiber更是相似于时间分片的方式,将任务拆分,将高优先级任务优先执行,提升页面渲染的流畅度等等,有不少须要咱们掌握的东西。

应了那句话:路漫漫其修远兮,吾将上下而求索。

欢迎点赞、留言、交流。

参考资料

相关文章
相关标签/搜索