1. 程式人生 > >React原始碼之元件的實現與首次渲染

React原始碼之元件的實現與首次渲染

react: v15.0.0 本文講 元件如何編譯 以及 ReactDOM.render 的渲染過程。
## babel 的編譯 babel 將 React JSX 編譯成 JavaScript. 在 babel 官網寫一段 JSX 程式碼編譯結果如圖: 每個標籤的建立都呼叫了 React.createElement.
## 原始碼中的兩種資料結構 貫穿原始碼,常見的兩種資料結構,有助於快速閱讀原始碼。 ### ReactElement 結構如下: ```js { $$typeof // ReactElement識別符號 type // 元件 key ref props // 元件屬性和children } ``` 是 React.createElement 的返回值。 ### ReactComponent ReactComponent 這個名字有點奇怪。
結構如下: ```js { _currentElement // ReactElement ... // 原型鏈上的方法 mountComponent, // 元件初次載入呼叫 updateComponent, // 元件更新呼叫 unmountComponent, // 元件解除安裝呼叫 } ``` 是 ReactCompositeComponent 的 instance 型別。其餘三種建構函式 ReactDOMComponent、ReactDOMTextComponent、ReactEmptyComponent 的例項結構與其相似。
## React.createElement React.createElement 實際執行的是 ReactElement.createElement。 ReactElement.createElement 接收三個引數, 返回 ReactElement 結構。 * type: string | Component * config: 標籤上的屬性 * ...children: children元素集合 重點關注 type 和 props。
然後看 ReactElement 方法,只是做了賦值動作。 綜上,我們寫的程式碼編譯後是這樣的: ```js 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 先來看下 ReactDOM.render 原始碼的執行過程

### instantiateReactComponent 在 _renderNewRootComponent 方法中,呼叫了 instantiateReactComponent,生成了的例項結構類似於 ReactComponent。 instantiateReactComponent 的引數是 node,node 的其中一種格式就是 ReactElement。 根據 node & node.type 的型別,會執行不同的方法生成例項 * ReactCompositeComponent * ReactDOMComponent * ReactDOMTextComponent * ReactEmptyComponent 簡化如下 ```js 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); } } ``` 通過四種方式例項化後的物件基本相似 ```js var instance = { _currentElement: node, _rootNodeID: null, ... } instance.__proto__ = { mountComponent, updateComponent, unmountComponent, } ``` 四種 mountComponent 簡化如下 #### ReactCompositeComponent ```js mountComponent: function () { // 建立當前元件的例項 this._instance = new this._currentElement.type(); // 呼叫元件的 render 方法,得到元件的 renderedElement renderedElement = this._instance.render(); // 呼叫 instantiateReactComponent, 得到 renderedElement 的例項化 ReactComponent this._renderedComponent = instantiateReactComponent(renderedElement); // 呼叫 ReactComponent.mountComponent return this._renderedComponent.mountComponent(); } ``` #### ReactDOMComponent react 原始碼中,插入 container 前使用 ownerDocument、DOMLazyTree 建立和存放節點,此處為了方便理解,使用 document.createElement 模擬。 ```js mountComponent: function () { var { type, props } = this._currentElement; // 建立dom 原始碼中使用 ownerDocument var element = document.createElement(type); // 遞迴children (原始碼中使用 DOMLazyTree 存放 並返回) if (props.children) { var childrenMarkups = props.children.map(function (node) { var instance = instantiateReactComponent(node); return instance.mountComponent(); }) element.appendChild(childrenMarkups) } return element; } ``` #### ReactDOMTextComponent ```js mountComponent: function () { return this._currentElement; } ``` #### ReactEmptyComponent ```js mountComponent: function () { return null; } ```
### ReactDOM.render 簡化 簡化如下: ```js ReactDOM.render = function (nextElement, container) { // 新增殼子 var nextWrappedElement = ReactElement( TopLevelWrapper, null, null, null, null, null, nextElement ); // 例項化 ReactElement var componentInstance = instantiateReactComponent(nextElement); // 遞迴生成html var markup = componentInstance.mountComponent; // 插入真實dom container.innerHTML = markup; } ```
## 總結 1. babel 將 JSX 語法編譯成 React.createElement 形式。 2. 原始碼中用到了兩個重要的資料結構 * ReactElement * ReactComponent 3. React.createElement 將我們寫的元件處理成 ReactElement 結構。 4. ReactDOM.render 傳入 ReactElement 和 container, 渲染流程如下 * 在 ReactElement 外套一層,生成新的 ReactElement * 例項化 ReactElement:var instance = instantiateReactComponent(ReactElement) * 遞迴生成 markup:var markup = instance.mountComponent() * 將 markup 插入 container:container.innerHTML = markup
[whosmeya.com](https://www.whosmey