雖然經常使用React、redux編寫SPA,可是這一塊是如何運做,應該如何優化,仍是比較困擾,最近開始閱讀程墨的《深刻淺出React和Redux》,結合以前讀過的React源碼和相關源碼的文章後,打算從源碼的角度,解釋下書中的一些內容。html
書中有一段話,關於組件從初始化到掛載通過的聲明週期:
流程:git
componentDidMount
以前調用的!!怪不得!每次在componentDidMount
裏調用異步的時候,render
裏面的object.xxx
從報錯!原來在componentDidMount
以前,已經render
過一次,而這個是後object
是個null
。很簡單的一串代碼,以下:github
class R extends Component {
constructor(props) {
super(props);
console.log('constructor');
this.state = {value: props.value}
}
componentWillMount() {
console.log('will mount');
}
componentDidMount() {
console.log('did mount');
}
render() {
console.log('render');
return (<div>{this.state.value}</div>);
}
}複製代碼
固然,瀏覽器暫時仍是無法識別ES6的語法的,因此須要經過babel編譯。通過babel編譯後以下:redux
var R = function (_Component) {
_inherits(R, _Component);
function R(props) {
_classCallCheck(this, R);
var _this = _possibleConstructorReturn(this, (R.__proto__ || Object.getPrototypeOf(R)).call(this, props));
console.log('constructor');
_this.state = { value: props.value };
return _this;
}
_createClass(R, [{
key: 'componentWillMount',
value: function componentWillMount() {
console.log('will mount');
}
}, {
key: 'componentDidMount',
value: function componentDidMount() {
console.log('did mount');
}
}, {
key: 'render',
value: function render() {
console.log('render');
return React.createElement(
'div',
null,
this.state.value
);
}
}]);
return R;
}(Component);複製代碼
從上面來看,其實我比較開心且興奮的看到了閉包和*原型、繼承,不清楚的小夥伴能夠github.com/mqyqingfeng… 這篇文章補補。
再來看看constructor,從編譯後的代碼來看constructor並非React原型的某個方法,而是babel轉譯後的這個下面的這塊函數。瀏覽器
function R(props) {
_classCallCheck(this, R);
var _this = _possibleConstructorReturn(this, (R.__proto__ || Object.getPrototypeOf(R)).call(this, props));
console.log('constructor');
_this.state = { value: props.value };
return _this;
}複製代碼
根據上面複習的原型、繼承, 這是個構造函數。super
對應的是_possibleConstructorReturn
.bash
這樣,咱們就明白了,爲何首先會調用constructor
了:constructor
並非React組件的原型函數,而是babel編譯後的一個構造函數。因此當實例化組件的時候,天然會先調用ES6中的constructor
了。babel
組件是如何插入到DOM中呢?先看看ES代碼:閉包
ReactDOM.render(
<R />,
document.getElementById('example')
);複製代碼
調用的是ReactDOM.render
追蹤源碼,實際調用的是ReactMount.render
。一層層追蹤後以下圖所示:app
圖片中黃色數字標識:步驟順序。藍色框表示裏面剩下的步驟都是在performInitialMount
完成的。
步驟按深度優先方式看。異步
從圖中,咱們能看到React組件週期,最先開始與performInitialMount
這個函數裏。其次是render函數,當執行完performInitialMount
後,跳出環境棧,接着執行componentDidMount
函數。
所以:最後的順序是constructor
-> componentWillMount
-> render
-> componentDidMount
書中還提到一句話:
render
函數並不作實際的渲染動做,他只是返回一個JSX描述的結構。render
函數被調用完以後,componentDidMount
函數並非會被馬上調用。componentDidMount
被調用的時候,render
函數返回的東西已經引起了渲染,組件已經被『裝載』到了DOM樹上
最直觀的從console.log
來看
ReactElement
。原來
render
返回的jsx的結構就是個
ReactElement
。其實,從babel那翻譯過來的js也能看出,
render
返回的是一個
ReactElement
{
key: 'render',
value: function render() {
console.log('render');
return React.createElement(
'div',
null,
this.state.value
);
}複製代碼
想要解釋第二句話,咱們得更加仔細的分析源碼:
class R extends Component {
constructor(props) {
super(props);
console.log('constructor');
this.state = {value: props.value}
}
componentWillMount() {
console.log('will mount');
}
componentDidMount() {
console.log('did mount');
}
render() {
console.log('render');
return (<div>{this.state.value}</div>);
}
}
ReactDOM.render(
<R />,
document.getElementById('example')
);複製代碼
首先,React會在R
上面再包裹一層,叫作_topLevelWrapper
的層,這個對象建立出來的是一個ReactCompositeComponentWrapper
對象,他基於ReactCompositeComponent
,以下:
var ReactCompositeComponentWrapper = function (element) {
this.construct(element);
};
_assign(ReactCompositeComponentWrapper.prototype, ReactCompositeComponent.Mixin, {
_instantiateReactComponent: instantiateReactComponent
});複製代碼
給instantiateReactComponent
打日誌,獲得以下:
最外層組件的TopLevelWrapper是個null,_renderedComponent
是組件R
,在下一步:
第二個是打印出來的R組件對應的ReactComponent對象,圖中能看到這個組件的下一個組件是ReactDOMComponent
。最後的打印出來的以下:
在圖1上稍微完善了下,另一個維度的大體調用流程以下圖:
爲何說大體調用流程圖,由於,由於在this.performInitialMount
函數裏有一個遞歸的過程。而後React組件除了ReactCompositeComponent
類以外,還有上面提到的ReactDOMComponent
、ReactEmptyComponent
和另一個內部類(這個類筆者不知道)。而圖中,咱們只是畫了一個ReactCompositeComponent
。
接下來,咱們就說說這個的調用流程把:
ReactMount內部的調用方式,圖上自認爲畫的已是至關清楚了,因此這裏不作詳細說明。
從圖中第3.1步驟開始提及:
一、在ReactMount中的mountComponentIntoNode
裏,調用了ReactReconciler.mountComponent
方法,方法中的wrapperInstance
這個時候,是ReactCompositeComponentWrapper
對象。返回一個markup(暫時不說)。那咱們看看ReactReconciler.mountComponent
這個方法是什麼。
二、在ReactReconciler.mountComponent
方法中,調用了是internalInstance.mountComponent
方法,也就是說咱們調用了『第1步』中的ReactCompositeComponentWrapper
對象的mountComponent
方法,那咱們再去ReactCompositeComponentWrapper.mountComponent
看看。
三、ReactCompositeComponent
筆者理解爲是一個組合組件,什麼是組合組件呢,自認爲就是把ReactDOMComponent
、ReactEmptyComponent
這兩種包含到一塊兒的組件。。。扯遠了,看看ReactCompositeComponent.mountComponent
作了什麼把。他調用了一個performInitialMount
方法。
四、那performInitialMount
方法幹了個啥???進去一看!重點來了!!他先看看組件有沒有componentWillMount
呀~有的話就調用。而後跳進_renderValidatedComponent
這個函數中去了!。好吧,那咱們就去_renderValidatedComponent
這個函數中看看。
五、一看發現,_renderValidatedComponent
這個函數又調用了_renderValidatedComponentWithoutOwnerOrContext
方法。
六、那就進去看看把,一看便知,_renderValidatedComponentWithoutOwnerOrContext
調用了組件的render
方法。var renderedComponent = inst.render();
,一看,render有個返回值!!!那這個返回值是什麼呢!!!打出來一看,圖三、4:
render
函數並不作實際的渲染動做,他只是返回一個JSX描述的結構(ReactElement)
七、帶着這個ReactElement
結構,咱們跳出了_renderValidatedComponentWithoutOwnerOrContext
函數,返回了『第5步』後,在_renderValidatedComponent
中,繼續返回了這個結構,返回到了第4步中的方法performInitialMount
中,執行到_renderValidatedComponent
以後。執行下面一句話:
this._renderedComponent = this._instantiateReactComponent(renderedElement);複製代碼
八、看過代碼的人,很清楚了_instantiateReactComponent
這個函數的主要做用就是根據不一樣ReactElement
,返回不一樣類型的ReactComponent
。接下來呢,在performInitialMount
又執行了
ReactReconciler.mountComponent(this._renderedComponent, .......);複製代碼
好了,咱們又開始了『第二步』的流程。可是注意,這個時候,仍是在當前這個組件的函數棧中。
九、假設,咱們renderedElement
返回結構是圖4的結構,這個時候的this._renderedComponent
那就是ReactDOMComponent
對象。那這時,ReactReconciler.mountComponent
裏就會調用ReactDOMComponent.mountComponent
十、ReactDOMComponent.mountComponent
返回的是一個圖5結構的同樣的東東(後面會叫他markup)。
ReactElement
和
ReactCompositeComponent
就是不同,感受親切多了!!廢話少說!
十一、既然『第10步』的ReactDOMComponent.mountComponent
調用完了,咱們就返回到『第9步』performInitialMount
裏,一看!執行完了,返回就是圖5的markup。那麼,這個函數出棧,回到『第3步』。
十二、ReactCompositeComponent
裏執行完performInitialMount
以後,就會調用componentDidMount
,看看他有沒有componentDidMount
這個函數。若是有的話,就會執行
transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);複製代碼
(通篇來看,這個調用的頻率仍是挺高的,具體作什麼,之後再說)
1三、ReactCompositeComponent.mountComponent
以後,ReactCompositeComponent.mountComponent
函數出棧,回到『第一步』:mountComponentIntoNode
。返回markup,以後。調用React._mountImageIntoNode
函數。這個函數裏,匆匆掃了一下關鍵字:發現,就是將『圖5』的markup結構,轉化成了對應的html結構。裏面有一個筆者認爲的這麼說的點睛之筆是setInnerHTML
。
綜上所屬:
咱們能夠得出的結論是:
constructor
->componentWillMount
->render
->componentDidMount
render
函數返回的是一個jsx的ReactElement結構。
至於第三個:
render
函數被調用完以後,componentDidMount
函數並非會被馬上調用。componentDidMount
被調用的時候,render
函數返回的東西已經引起了渲染,組件已經被『裝載』到了DOM樹上。
筆者還得看看~~ 不過相信,這句話確定是對的!要否則,怎麼會出書,要否則爲何叫作componentDidMount
,函數字面意思,就是render以後,先插入DOM,再調用componentDidMount
函數。看來,關鍵的地方在transaction.getReactMountReady().enqueue(inst.componentDidMount, inst);
這句上。
這也是接下來須要研究的。
限於文章不能寫太長。先暫時這樣,拋出幾個待討論的點,省的忘記。
感受不少話說的很重複,可是筆者就是想從不一樣角度說地更仔細點。文章栗子有限,說的只是大概。若有說不明白的或者說錯的地方,麻煩指出。由於,筆者是一個熱愛學習,追求進步的北京打工的外地人!!!!!!