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