接上文,
React流程圖:
https://bogdan-lyashenko.gith…
最後的最後
更新方法基於子元素上的多個屬性去處理子元素。這裡會有幾種場景,但是技術上來說主要是兩種。一種是子元素仍然是‘複雜’物件,也就是說子元素還是React元件,React需要遞迴處理巢狀的子元件直到到達他們的內容層級。還有一種,就是子元素已經是內容層級裡,內容就是字串或者數字或者其他簡單型別。
處理方式是根據nextProps.children的型別來判斷的。在我們的列子中,元件
<ExampleApplication>元件有三個子元素,button,childCmp 和 text string。
我們看下它是如何運作的。
在Examplication子元素的第一次迭代期間,子元素的型別不是內容,所以需要進入到‘複雜’元件的處理邏輯。我們遍歷所有的子元素,按之前處理它們父元素的過了處理它們。順便提下,驗證shouldUpdateReactComponent的程式碼塊會令人有點疑惑,表面上看起來這個是用來檢測是否需要更新,但是實際上,它除了檢測更新,還檢測刪除,新建操作(為了簡化流程圖,相應的程式碼沒有展示在流程圖中)。之後,我們將舊元素和當前元素,如果一些子元素被移除了,則我們需要解除安裝對應的元件並同時移除它。
接下去,在第二次迭代過程中,我們需要處理button,這個相對來說比較簡單,因為button的子元素就是文字,內容就是‘set state button`。我們檢測下之前的內容是否跟現在的保持一致,嗯,文字沒有發生改變,所以我們不需要更新button。邏輯上來說這樣很正確,其實這就是虛擬DOM的作用,現在虛擬DOM聽起來就具體了些,是不是?React會維護內部DOM,同時只在需要時才去處理真正的DOM節點,通過這種方式,很自然的會提高效能。到這裡,你應該已經能理解React的設計思想了,這之後,我們對ChildCmp進行更新,它的子元素會被遍歷直到它的內容層級並進行更新。在我們的列子裡,通過click和setState的呼叫,`click state message`會對this.props.message進行更新。
//...
onClickHandler() {
this.setState({ message: `click state message` });
}
render() {
return <div>
<button onClick={this.onClickHandler.bind(this)}>set state button</button>
<ChildCmp childMessage={this.state.message} />
//...
現在我們要對元素內容進行更新,事實上,是替換它的內容。那麼是如何進行更新的呢?一個類似擁有配置資訊的配置物件會被解析,並且配置物件裡的定義的動作會被執行。對應我們的例子,文字更新的配置會像如下:
{
afterNode: null,
content: "click state message",
fromIndex: null,
fromNode: null,
toIndex: null,
type: "TEXT_CONTENT"
}
正如你所見,它幾乎就是一個空物件,這個文字內容更新例子是相當直白的。配置物件裡有很多欄位,這是因為在對DOM節點進行移動時,配置物件會比文字更新的配置物件相對來說會更復雜些:
看下原始碼,這應該會讓我們有個更清晰的認識:
//src
enderersdomclientutilsDOMChildrenOperations.js#172
processUpdates: function(parentNode, updates) {
for (var k = 0; k < updates.length; k++) {
var update = updates[k];
switch (update.type) {
case `INSERT_MARKUP`:
insertLazyTreeChildAt(
parentNode,
update.content,
getNodeAfter(parentNode, update.afterNode)
);
break;
case `MOVE_EXISTING`:
moveChild(
parentNode,
update.fromNode,
getNodeAfter(parentNode, update.afterNode)
);
break;
case `SET_MARKUP`:
setInnerHTML(
parentNode,
update.content
);
break;
case `TEXT_CONTENT`:
setTextContent(
parentNode,
update.content
);
break;
case `REMOVE_NODE`:
removeChild(parentNode, update.fromNode);
break;
}
}
}
我們的例項會走到`TEXT_CONTENT`的分支裡,然後這就是最後一步了,React呼叫setTextContent方法,此方法會對真正的DOM節點進行內容更改。
不錯不錯,最終內部被更新到了頁面上,這就是一個重繪的過程了。還有什麼東西沒有講到嗎?嗯,不要著急,讓我們先完成這個更新過程。所有的東西都已經準備完畢,所有我們元件的componentDidUpdate方法會被呼叫。那麼,這種延遲呼叫是如何實現的呢?沒錯,就是使用事務包裝器。就像之前提到的,‘髒’元件的更新是被ReactUpdateFlushtransaction給包裝過的,其中一個包裝器裡就有呼叫this.callbackQueue.notifyAll的邏輯,也就是在這裡,componentDidUpdate會被呼叫。完美!
現在,我們真的完成了整個過程。