React的簡單實現(一)JSX和虛擬Dom

createElement函數

在react里面JSX會被轉譯成用React.createElement方法包裹的代碼,比如

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

會經過一個語法分析的庫(babel里面)參考:http://www.lxweimin.com/p/7e872afeae42
,最終被轉化為:

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

不管JSX,先簡單實現一下createElement這個函數,從jsx轉譯結果來看,createElement方法的參數是這樣:

createElement( tag, attrs, child1, child2, child3 );

第一個參數是DOM的標簽名,比如div,h1,span等等
第二個參數是一個對象,里面包含了所有的屬性,可能包含了className,id等等
第三個參數開始,就是它的子節點

最簡單實現,返回一個對象進行包裹:

function createElement( tag, attrs, ...children ) {
    return {
        tag,
        attrs,
        children
    }
}

試著進行封裝和調用:

const React = {
    createElement
}

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

這時候會遇到上面的錯誤,因為目前我們的語法是JSX,所以需要引入babel的支持,參考:https://babeljs.io/docs/en/,這里我們采用一種更簡單的方式,單純對JSX進行轉換

1,新建一個.babelrc文件,指定transform-react-jsx用的方法就是上述的createElement:

{
  "presets": ["env"],
  "plugins": [
    ["transform-react-jsx", {
      "pragma": "React.createElement"
    }]
  ]
}

2, 使用parcel打包工具,對jsx語法進行詞法分析,然后用上述的createElement包裹起來,先進行安裝:

npm install -g parcel-bundler

3,調整代碼結構,新建index.html和index.js,js內容就是上面的


image.png

4,使用 ‘parcel index.html’ 命令進行打包操作
5,打包完成后得到dist的輸出


image.png

運行index.html可以得到下述結果(可能需要更改下js的路徑)


image.png
ReactDOM.render方法

參考react里面的寫法

ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('root')
);

實際上是JSX進過轉換是這樣的

ReactDOM.render(
    React.createElement( 'h1', null, 'Hello, world!' ),
    document.getElementById('root')
);

render方法的作用就是將對象轉換成虛擬DOM,再最終渲染成真實的DOM

function render( vnode, container ) {
    
    // 當vnode為字符串時,渲染結果是一段文本
    if ( typeof vnode === 'string' ) {
        const textNode = document.createTextNode( vnode );
        return container.appendChild( textNode );
    }

    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 container.appendChild( dom );    // 將渲染結果掛載到真正的DOM上
}

還有setAttribute方法:

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 );
        }
    }
}

每次render前,清理一下之前的內容:

const ReactDOM = {
    render: ( vnode, container ) => {
        container.innerHTML = '';
        return render( vnode, container );
    }
}

在index.html里面增加容器:<div id="root"></div>
重新 parcel index.html 運行,得到輸出結果:

image.png

源碼地址:https://github.com/liuxiaocong/dailyMove/tree/master/simpleReact

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。