react: v15.0.0node
本文講 組件如何編譯 以及 ReactDOM.render 的渲染過程。react
babel 將 React JSX 編譯成 JavaScript.babel
在 babel 官網寫一段 JSX 代碼編譯結果如圖:數據結構
每一個標籤的建立都調用了 React.createElement.app
貫穿源碼,常見的兩種數據結構,有助於快速閱讀源碼。dom
結構以下:函數
{ $$typeof // ReactElement標識符 type // 組件 key ref props // 組件屬性和children }
是 React.createElement 的返回值。this
ReactComponent 這個名字有點奇怪。spa
結構以下:code
{ _currentElement // ReactElement ... // 原型鏈上的方法 mountComponent, // 組件初次加載調用 updateComponent, // 組件更新調用 unmountComponent, // 組件卸載調用 }
是 ReactCompositeComponent 的 instance 類型。其他三種構造函數 ReactDOMComponent、ReactDOMTextComponent、ReactEmptyComponent 的實例結構與其類似。
React.createElement 實際執行的是 ReactElement.createElement。
ReactElement.createElement 接收三個參數:
重點關注 type 和 props。
而後看 ReactElement 方法,只是作了賦值動做。
綜上,咱們寫的代碼編譯後是這樣的:
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 源碼的執行過程
在 _renderNewRootComponent 方法中,調用了 instantiateReactComponent,生成了的實例結構相似於 ReactComponent。
instantiateReactComponent 的參數是 node,node 的其中一種格式就是 ReactElement。
根據 node & node.type 的類型,會執行不一樣的方法生成實例
簡化以下
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); } }
經過四種方式實例化後的對象基本類似
var instance = { _currentElement: node, _rootNodeID: null, ... } instance.__proto__ = { mountComponent, updateComponent, unmountComponent, }
四種 mountComponent 簡化以下
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(); }
react 源碼中,插入 container 前使用 ownerDocument、DOMLazyTree 建立和存放節點,此處爲了方便理解,使用 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; }
mountComponent: function () { return this._currentElement; }
mountComponent: function () { return null; }
簡化以下:
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; }