Bootstrap

React(二)实现自定义createElement和render

上一篇介绍了JSX的概念和简单实例:React(一)JSX概念和实例_Zoie_ting的博客-CSDN博客

一,创建React和ReactDOM

        创建一个新的js,笔者这里叫treact.js,将原来的import React from 'react';修改为import React from './treact.';

import ReactDOM from 'react-dom';
import React from './treact';

const Comp = (props) => {
    return (
        <div>
            <p>{props.name}</p>
        </div>
    )
};

const jsx = (
    <div id="demo">
        <p>hello</p>
        <Comp name="world"></Comp>
    </div>
);
ReactDOM.render(jsx,document.getElementById('root'));

treact.js:

function createElement() {
    console.log(arguments)
}
export default { createElement };

        运行一下,在控制台看到输出的内容是从里到外的,且第一个参数是类型,第二个是属性,第三是孩子:

        因此,先实现让 createElement 能够输出类似的结构:

treact.js:

function createElement(type,props,...children) {
    props.children = children;
    delete props.__self;
    delete props.__source;
    console.log({type,props});
    return {type,props}
}
export default { createElement };

treact-dom.js:

function render(vnode,el) {
    el.innerHTML = JSON.stringify(vnode)
    
}
export default { render }

 输出如下:

同样的由内向外,最后页面的显示是我们自己写的json字符串格式。

二,虚拟节点转化为真实DOM

1、确定转换的节点类型

  • html标签:type是字符串
  • function组件:type是function
  • class组件:type也是function,打上标签,创建的时候表明是类组件
class Component {
    static isComponent = true;
    constructor(type,props){
        this.props = props;
        this.type = type;
    }
}
//...

function createElement(type,props,...children) {
   //...
    let vtype;//虚拟节点类型
    if(typeof type === 'string'){//html标签
        vtype = 1
    } else if(typeof type === 'function'){
        if(type.isComponent) {//类组件
            vtype = 2;
        } else {//函数组件
            vtype = 3;
        }
    }
    return {vtype,type,props}
}

此时看一下输出:

 2、处理不同节点类型

        在render函数中,初始化渲染虚拟节点,新建initVnode函数,对三种情况分别处理,但实际上最终还是回到创建html中去:

function render(vnode,el) {
    let node = initVnode(vnode);
    el.append(node)
}
function initVnode(vnode) {
    const {vtype} = vnode;
    if(!vtype) {
        return document.createTextNode(vnode);
    }
    if(vtype === 1) {
        return createElement(vnode)
    }
    if(vtype === 2) {
        return createClassElement(vnode);
    }
    if(vtype === 3) {
        return createFuncElement(vnode);
    }
}

        创建html非常简单,常规操作,创建type类型的dom,给dom加上属性即可,需要注意的是有些特别属性,react写法不同,例如class属性写作className,style使用对象,需要把对象处理成字符串,且css属性名称也要从驼峰改成连字符,这里笔者不考虑那么多情况,直接加属性了:

function createElement(vnode) {
    const { type, props } = vnode;
    let node = document.createElement(type);
    const { children, ...rest } = props;
    Object.keys(rest).forEach((attr) => {
        if (attr === 'style'){
            node.setAttribute('style',Object.keys(rest[attr]).map((a) => a + ':' +rest[attr][a]).join(';'));
        } else {
            node.setAttribute(attr,rest[attr]);
        }
    });
    return node;
}

        对于函数组件和类组件,大同小异。

        类组件只需要new一个相同的类,即props中的type,例如:new type(props)。由此得到一个类,但类组件通过render函数返回JSX,所以只需要调用一下这个类的render()就可以得到虚拟节点,再把虚拟节点传入initVode()......最终回到createElement()。

        函数组件只需要执行一下该函数:type(props)即可拿到JSX,再把虚拟节点传入initVode()......最终回到createElement()。

        注意,一定要return!!!一定要return!!!一定要return!!!

function createClassElement(vnode) {
    const { type, props } = vnode;
    const component = new type(props);
    const vdom = component.render();
    return initVnode(vdom);
}
function createFuncElement(vnode) {
    const { type, props } = vnode;
    const vdom = type(props);
    return initVnode(vdom);
}

        由于虚拟节点的树形结构,因此,在createElement中需要递归,将返回的节点挂载到当前节点上来:

    children.forEach((vdom) => {
        let dom = initVnode(vdom);
        dom && node.appendChild(dom);
    });

总结

        本篇介绍了React.createElement和ReactDOM.render的实现思路:

  1. 在createElement中传入节点类型、属性、子节点,通过添加vtype给节点打上虚拟节点类型标签;
  2. 在render时根据vtype类型将虚拟节点转为真实节点,最终挂载到dom元素上去。

感兴趣可以继续看下一篇:React(三)自定义render处理map的虚拟节点和diff算法_Zoie_ting的博客-CSDN博客

;