【Under-the-hood-ReactJS-Part14】React原始碼解讀

越山發表於2018-07-05

接上文,

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會被呼叫。完美!

現在,我們真的完成了整個過程。

相關文章