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節點的標簽名,它的值可能是div,h1,span等等
第二個參數是一個對象,里面包含了所有的屬性,可能包含了className,id等等
從第三個參數開始,就是它的子節點

我們對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 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中添加一個根節點

  我們先來試試官方文檔中的Hello,World
ReactDOM.render(
    <h1>Hello, world!</h1>,
    document.getElementById('root')
);```

運行以后 效果基本就出來了


免責聲明!

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



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