react系列一,react虚拟dom如何转成真实的dom

react,想必做为前端开发必定不陌生,组件化以及虚拟dom使得react成为最受欢迎额前端框架之一。咱们知道react是基于虚拟dom的,可是什么是虚拟dom呢,其实就是一组js对象,那么咱们今天就来认识什么是虚拟dom,以及如何转成真实的dom结构,完整的 简易版react  在我的github,实现了diff算法,组件渲染,组件更新,钩子函数。javascript

一.认识虚拟domcss

首先咱们看以下代码html

const title = <h1 className="title">Hello, world!</h1>;

这并非合法的js代码,它是一种被称为jsx的语法扩展,经过它咱们就能够很方便的在js代码中书写html片断。前端

本质上,jsx是语法糖,上面这段代码会被babel转换成以下代码java

咱们下载插件 babel-plugin-transform-react-jsx,而且配置.babelrc文件node

{
    "presets": ["env"],
    "plugins": [
        ["transform-react-jsx", {
            "pragma": "React.createElement"//大部分框架喜欢改为h
        }]
    ]
}

因而页面中的jsx就会被babel转成以下的结构react

const title = React.createElement(
    'h1',
    { className: 'title' },
    'Hello, world!'
);

能够看出babel已经把一个dom元素分解成标签名称h1,属性集合对象,以及内部子节点(这里是hello world文本节点),咱们首先修改这个方法,为了转成咱们须要的结构git

function createElement( tag, attrs, ...children ) {
    return {
        tag,
        attrs,
        children
    }
}
// 将上文定义的createElement方法放到对象React中
const React = {
    createElement,
}

函数的参数...children使用了ES6的rest参数,它的做用是将后面child1,child2等参数合并成一个数组children。es6

如今咱们来试试调用它,一下结构都是babel自动调用React.createElement给咱们转成的,固然你也能够本身写方法将真实的dom转为js对象github

const element = (
    <div>
        hello<span>world!</span>
    </div>
);
console.log( element );

  

二.将上列的虚拟dom结构转成真实的dom

1.若是遇到文本节点则直接返回新建的文本节点

//处理文本节点
    if( typeof vnode === 'string'){
        const textNode = document.createTextNode( vnode )
        return textNode;
    }

2.处理普通的元素

//普通的dom
    const dom = document.createElement( vnode.tag );
    if( vnode.attrs ){
        Object.keys( vnode.attrs ).forEach( key => {
            const value = vnode.attrs[ key ];
            setAttribute( dom, key, value );    // 设置属性
        } );
    }
    vnode.children.forEach( child => render( child, dom ) );    // 递归渲染子节点
    return dom ;    // 返回虚拟dom为真正的DOM

3.遇到普通元素的属性,须要这是属性节点,可是分为两种,一种是普通的属性,好比className,另外一种是方法绑定,好比是onClick

function setAttribute( dom, name, value ) {
    // 若是属性名是className,则改回class
    if ( name === 'className' ) name = 'class';

    // 若是属性名是onXXX,则是一个事件监听方法
    if ( /on\w+/.test( name ) ) {
        name = name.toLowerCase();
        dom[ name ] = value || '';
    // 若是属性名是style,则更新style对象
    } else if ( name === 'style' ) {
        if ( !value || typeof value === 'string' ) {
            dom.style.cssText = value || '';
        } else if ( value && typeof value === 'object' ) {
            for ( let name in value ) {
                // 能够经过style={ width: 20 }这种形式来设置样式,能够省略掉单位px
                dom.style[ name ] = typeof value[ name ] === 'number' ? value[ name ] + 'px' : value[ name ];
            }
        }
    // 普通属性则直接更新属性
    } else {
        if ( name in dom ) {
            dom[ name ] = value || '';
        }
        if ( value ) {
            dom.setAttribute( name, value );
        } else {
            dom.removeAttribute( name, value );
        }
    }
}

 

三.查看完整的代码

function render ( vnode, container ){
    return container.appendChild( _render( vnode ) );
}
function _render( vnode ){
    if ( typeof vnode === 'number' ) {
        vnode = String( vnode );
    }
    //处理文本节点
    if( typeof vnode === 'string'){
        const textNode = document.createTextNode( vnode )
        return textNode;
    }
    //处理组件
    if ( typeof vnode.tag === 'function' ) {
        const component = createComponent( vnode.tag, vnode.attrs );
        setComponentProps( component, vnode.attrs );
        return component.base;
    }
    //普通的dom
    const dom = document.createElement( vnode.tag );
    if( vnode.attrs ){
        Object.keys( vnode.attrs ).forEach( key => {
            const value = vnode.attrs[ key ];
            setAttribute( dom, key, value );    // 设置属性
        } );
    }
    vnode.children.forEach( child => render( child, dom ) );    // 递归渲染子节点
    return dom ;    // 返回虚拟dom为真正的DOM
}
//实现dom挂载到页面某个元素
const ReactDOM = {
    render: ( vnode, container ) => {
        container.innerHTML = '';
        return render( vnode, container );
    }
}

如今咱们已经实现将虚拟dom转为真实的dom,已经绑定属性,咱们如今来像react同样调用这个方法

const element = (
    <div>
        hello<span>world!</span>
    </div>
);

ReactDOM.render(
    element,
    document.getElementById( 'main' )
);

如今就实现往页面中元素id为main的元素上挂载了该element。

相关文章
相关标签/搜索