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。

相關文章
相關標籤/搜索