沒有用到React,為什么我需要import引入React?


沒有用到React,為什么我需要import引入React?

本質上來說JSX是React.createElement(component, props, ...children)方法的語法糖。

所以我們如果使用了JSX,我們其實就是在使用React,所以我們就需要引入React

前言

React是前端最受歡迎的框架之一,解讀其源碼的文章非常多,但是我想從另一個角度去解讀React:從零開始實現一個React,從API層面實現React的大部分功能,在這個過程中去探索為什么有虛擬DOM、diff、為什么setState這樣設計等問題。

提起React,總是免不了和Vue做一番對比

Vue的API設計非常簡潔,但是其實現方式卻讓人感覺是“魔法”,開發者雖然能馬上上手,但其原理卻很難說清楚。

相比之下React的設計哲學非常簡單,雖然有很多需要自己處理的細節問題,但它沒有引入任何新的概念,相對更加的干凈和簡單。

關於jsx

在開始之前,我們有必要搞清楚一些概念。

我們來看一下這樣一段代碼:

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

  

這段代碼並不是合法的js代碼,它是一種被稱為jsx的語法擴展,通過它我們就可以很方便的在js代碼中書寫html片段。

本質上,jsx是語法糖,上面這段代碼會被babel轉換成如下代碼:

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

  

React.createElement和虛擬DOM

前文提到,jsx片段會被轉譯成用React.createElement方法包裹的代碼。所以第一步,我們來實現這個React.createElement方法

從jsx轉譯結果來看,createElement方法的參數是這樣:

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

  

第一個參數是DOM節點的標簽名,它的值可能是divh1span等等
第二個參數是一個對象,里面包含了所有的屬性,可能包含了classNameid等等
從第三個參數開始,就是它的子節點

我們對createElement的實現非常簡單,只需要返回一個對象來保存它的信息就行了。

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

  

函數的參數 ...children使用了ES6的rest參數,它的作用是將后面child1,child2等參數合並成一個數組children。

現在我們來試試調用它

// 將上文定義的createElement方法放到對象React中
const React = {
    createElement
}

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

  打開調試工具,我們可以看到輸出的對象和我們預想的一致

我們的createElement方法返回的對象記錄了這個DOM節點所有的信息,換言之,通過它我們就可以生成真正的DOM,這個記錄信息的對象我們稱之為虛擬DOM。

ReactDOM.render

接下來是ReactDOM.render方法,我們再來看這段代碼

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

  經過轉換,這段代碼變成了這樣

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

  

所以render的第一個參數實際上接受的是createElement返回的對象,也就是虛擬DOM
而第二個參數則是掛載的目標DOM

總而言之,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函數時,不會清除原來的內容。所以我們將其附加到ReactDOM對象上時,先清除一下掛載目標DOM的內容:

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

  

渲染和更新

到這里我們已經實現了React最為基礎的功能,可以用它來做一些事了。

我們先在index.html中添加一個根節點

<div id="root"></div>

  我們先來試試官方文檔中的Hello,World

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

  可以看到結果:

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM