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

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

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

JSX

jsx是个啥?

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

为什么要引入jsx?

写js不香吗?为什么要引入一个新概念jsx来迷惑大家?概括来讲jsx来讲有以下几个好处:

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

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

jsx预处理前:

jsx预处理后

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

接下来我们就自己动手来实现这三个API吧!

核心API实现(简版)

createElement和Component

作用:将传入的节点转化成vdom。

(1)创建./simple-react/component.js。实现class组件必备条件。

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;

上面我只是简单使用了1、2、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 (
        
{props.name}
) } class ClassComp extends React.Component { constructor(props) { super(props); this.state = { } } handleClick=()=>{ alert("hello") } render() { return (

{this.props.name}

{ [1,2,3].map(item=> (

{item}

) ) }
); } } let jsx=(
我是内容
) 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更是类似于时间分片的方式,将任务拆分,将高优先级任务优先执行,提高页面渲染的流畅度等等,有很多需要我们掌握的东西。

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

欢迎点赞、留言、交流。

参考资料