React源碼之組件的實(shí)現(xiàn)與首次渲染

react: v15.0.0

本文講 組件如何編譯 以及 ReactDOM.render 的渲染過(guò)程。


babel 的編譯

babel 將 React JSX 編譯成 JavaScript.

在 babel 官網(wǎng)寫(xiě)一段 JSX 代碼編譯結(jié)果如圖:

每個(gè)標(biāo)簽的創(chuàng)建都調(diào)用了 React.createElement.


源碼中的兩種數(shù)據(jù)結(jié)構(gòu)

貫穿源碼,常見(jiàn)的兩種數(shù)據(jù)結(jié)構(gòu),有助于快速閱讀源碼。

ReactElement

結(jié)構(gòu)如下:

{ $$typeof // ReactElement標(biāo)識(shí)符 type // 組件 key ref props // 組件屬性和children }

是 React.createElement 的返回值。

ReactComponent

ReactComponent 這個(gè)名字有點(diǎn)奇怪。

結(jié)構(gòu)如下:

{ _currentElement // ReactElement ... // 原型鏈上的方法 mountComponent, // 組件初次加載調(diào)用 updateComponent, // 組件更新調(diào)用 unmountComponent, // 組件卸載調(diào)用 }

是 ReactCompositeComponent 的 instance 類(lèi)型。其余三種構(gòu)造函數(shù) ReactDOMComponent、ReactDOMTextComponent、ReactEmptyComponent 的實(shí)例結(jié)構(gòu)與其相似。


React.createElement

React.createElement 實(shí)際執(zhí)行的是 ReactElement.createElement。

ReactElement.createElement 接收三個(gè)參數(shù):

  • type: string | Component
  • config: 標(biāo)簽上的屬性
  • ...children: children元素集合

重點(diǎn)關(guān)注 type 和 props。

然后看 ReactElement 方法,只是做了賦值動(dòng)作。

綜上,我們寫(xiě)的代碼編譯后是這樣的:

class C extends React.Component { render() { return { type: "div", props: { children: this.props.value, }, }; } } class App extends React.Component { render() { return { type: "div", props: { children: [ { type: "span", props: { children: "aaapppppp", }, }, "123", { type: C, props: { value: "ccc", }, }, ] }, }; } } ReactDOM.render( { type: App, props: {}, }, document.getElementById("root") );

ReactDOM.render

先來(lái)看下 ReactDOM.render 源碼的執(zhí)行過(guò)程


instantiateReactComponent

在 _renderNewRootComponent 方法中,調(diào)用了 instantiateReactComponent,生成了的實(shí)例結(jié)構(gòu)類(lèi)似于 ReactComponent。

instantiateReactComponent 的參數(shù)是 node,node 的其中一種格式就是 ReactElement。

根據(jù) node & node.type 的類(lèi)型,會(huì)執(zhí)行不同的方法生成實(shí)例

  • ReactCompositeComponent
  • ReactDOMComponent
  • ReactDOMTextComponent
  • ReactEmptyComponent

簡(jiǎn)化如下

var instantiateReactComponent = function (node) { if (node === null || node === false) { return new ReactEmptyComponent(node); } else if (typeof node === 'object') { if (node.type === 'string') { return new ReactDOMComponent(node); } else { return new ReactCompositeComponent(node); } } else if (typeof node === 'string' || typeof node === 'number') { return new ReactDOMTextComponent(node); } }

通過(guò)四種方式實(shí)例化后的對(duì)象基本相似

var instance = { _currentElement: node, _rootNodeID: null, ... } instance.__proto__ = { mountComponent, updateComponent, unmountComponent, }

四種 mountComponent 簡(jiǎn)化如下

ReactCompositeComponent

mountComponent: function () { // 創(chuàng)建當(dāng)前組件的實(shí)例 this._instance = new this._currentElement.type(); // 調(diào)用組件的 render 方法,得到組件的 renderedElement renderedElement = this._instance.render(); // 調(diào)用 instantiateReactComponent, 得到 renderedElement 的實(shí)例化 ReactComponent this._renderedComponent = instantiateReactComponent(renderedElement); // 調(diào)用 ReactComponent.mountComponent return this._renderedComponent.mountComponent(); }

ReactDOMComponent

react 源碼中,插入 container 前使用 ownerDocument、DOMLazyTree 創(chuàng)建和存放節(jié)點(diǎn),此處為了方便理解,使用 document.createElement 模擬。

mountComponent: function () { var { type, props } = this._currentElement; var element = document.createElement(type); if (props.children) { var childrenMarkups = props.children.map(function (node) { var instance = instantiateReactComponent(node); return instance.mountComponent(); }) element.appendChild(childrenMarkups) } return element; }

ReactDOMTextComponent

mountComponent: function () { return this._currentElement; }

ReactEmptyComponent

mountComponent: function () { return null; }

ReactDOM.render 簡(jiǎn)化

簡(jiǎn)化如下:

ReactDOM.render = function (nextElement, container) { var nextWrappedElement = ReactElement( TopLevelWrapper, null, null, null, null, null, nextElement ); var componentInstance = instantiateReactComponent(nextElement); var markup = componentInstance.mountComponent; container.innerHTML = markup; }

總結(jié)

  1. babel 將 JSX 語(yǔ)法編譯成 React.createElement 形式。
  2. 源碼中用到了兩個(gè)重要的數(shù)據(jù)結(jié)構(gòu)
    • ReactElement
    • ReactComponent
  3. React.createElement 將我們寫(xiě)的組件處理成 ReactElement 結(jié)構(gòu)。
  4. ReactDOM.render 傳入 ReactElement 和 container, 渲染流程如下
    • 在 ReactElement 外套一層,生成新的 ReactElement
    • 實(shí)例化 ReactElement:var instance = instantiateReactComponent(ReactElement)
    • 遞歸生成 markup:var markup = instance.mountComponent()
    • 將 markup 插入 container:container.innerHTML = markup


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容