手写MiniReact(1)—— React.createElement() 和ReactDOM.render()

278 阅读2分钟

本文旨在记录react框架源码的学习历程。手写一个mini版的react,实现主要的功能。 这是第一篇,主要内容包括React.createElement和ReactDOM.render两个函数的实现思路。

关于jsx

可以理解为一个js的语法糖,使用jsx的编写方式,可以像写html标签一样,更加灵活便捷。经过babel的编译后,其实是调用了React.createElement()生成了虚拟DOM

image.png

所以jxs就是原生js的语法糖,简化了创建虚拟DOM的操作

关于 React.createElement

React.createElement(type, config, children) 该函数的返回值就是虚拟DOM, 三个参数分别对应 标签名称,标签属性,标签体内容 其中config包含了key、ref、className、style等等以props形式传递给元素的内容。

image.png

返回的虚拟DOM整体上和config类似,但有一点区别,比如标签体内容、className、style等被装进了props对象中。对比两幅图,可以实现简单的React.createElement

创建一个 react.js 文件

function createElement(type, config, children){
    let key = null, ref = null

    if(config){
        key = config.key
        ref = config.ref
        delete config.key
        delete config.ref
    }

    let props = {...config}

    if(arguments.length > 3){               //说明children有多个,应该封装成数组
        let arr = [...arguments]
        props.children = arr.slice(2)
    }else if(arguments.length === 3){        //说明只有一个子节点,直接添加
        props.children = children
    }

    return {
        $$typeof: REACT_ELEMENT,
        props,
        key, 
        ref,
        type
    }
}

const React = {
    createElement,
}

export default React

关于 ReactDOM.render

ReactDOM.render(VDOM, container) 该函数两个作用,一个是接收虚拟DOM, 创建出真实DOM;另外一个是把真实DOM挂载到指定的容器中 创建一个 react-dom.js 文件。 这个过程中用到了一个递归去创建节点

function render(VDOM, container) {       //把虚拟DOM转为真实DOM   把真实DOM挂到指定容器
    let dom = createDOM(VDOM)       //根据虚拟DOM生成真实DOM
    container.appendChild(DOM)      //将真实DOM挂载到了页面
}

function createDOM(VDOM){       //VDOM: {type, props, ref, key}

    if(Object(VDOM) !== VDOM){         //如果是文本类型的,就直接创建文本节点,也是递归的出口
        return document.createTextNode(VDOM)
    } 

    let {type, ref, key, props} = VDOM

    let dom = document.createElement(type)   //根据type创建真正的dom

    updateProps(dom, null, props)

    let children = props.children
    if(children){                       // <span></span>  这种情况的话 props:{}
        handleChildren(dom, children)
    }
    
    return dom
}

function updateProps(dom, oldProps, newProps){      //props:{children, className, style}
    for(let key in newProps){
        if(key === "children"){
            continue
        }else if(key === "style"){
            let styleObject = newProps[key]
            for(let item in styleObject){
                dom.style[key] = styleObject[item]
            }
        }else if(key.startsWith("on")){     //说明是绑定的事件
            dom[key.toLocaleLowerCase()] = newProps[key]
        }else{
            dom[key] = newProps[key]
        }
    }

    if(oldProps){           //更新处理。如果旧的属性在新的属性中不存在,那么就删除
        for(let key in oldProps){
            if(!newProps[key])  delete oldProps[key]
        }
    }
}

//形成递归  children:["hello, react", { xxxx,xxxx VDOM }]
function handleChildren(dom, children){     
    if(children instanceof Array){
        children.forEach( child => render(child, dom))
    }else{
        render(child, dom)
    }
}

示例

在React脚手架文件的 index.js

import React from "./react-resource/react"       //引入刚刚写的react和react-dom
import ReactDOM from "./react-resource/react-dom"


//jsx语法
let element1 = <h1 className="title" style={{ color: "red" }}>
  hello, react
  <span>this is span</span>
</h1>   

ReactDOM.render(element1, document.getElementById("root"))

效果展示

image.png