跳到主要內容

Render、Commit 和 Mount

注意

本文檔指的是正在積極推廣的全新架構

React Native 渲染器會經過一系列工作,將 React 邏輯渲染到宿主平台。這一系列工作稱為渲染管線,發生在初始渲染和 UI 狀態更新時。本文檔將介紹渲染管線,以及在這些情況下的差異。

渲染管線可以分為三個主要階段

  1. Render(渲染): React 執行產品邏輯,在 JavaScript 中建立 React 元素樹。渲染器從此樹狀結構在 C++ 中建立 React Shadow 樹
  2. Commit(提交):在完整建立 React Shadow 樹後,渲染器會觸發提交。這會提升 React 元素樹和新建立的 React Shadow 樹,使其成為要掛載的「下一個樹」。這也會排程計算其版面配置資訊。
  3. Mount(掛載): React Shadow 樹現在有了版面配置計算結果,將轉換為 宿主視圖樹

渲染管線的階段可能會在不同的執行緒上發生。有關更多詳細資訊,請參閱執行緒模型文件。

React Native renderer Data flow


初始渲染

假設您想要渲染以下內容

jsx
function MyComponent() {
return (
<View>
<Text>Hello, World</Text>
</View>
);
}

// <MyComponent />

在上面的範例中,<MyComponent /> 是一個 React 元素。React 透過調用它(或是它的 render 方法,如果使用 JavaScript 類別實作)遞迴地將這個React 元素簡化為終端 React 宿主組件,直到每個React 元素都無法再簡化為止。現在您擁有一個由 React 宿主組件組成的React 元素樹

階段 1. Render(渲染)

Phase one: render

在這個元素簡化過程中,當每個React 元素被調用時,渲染器也會同步建立一個 React Shadow 節點。這只發生在React 宿主組件上,而不是 React 複合組件。在上面的範例中,<View> 會導致建立 ViewShadowNode 物件,而 <Text> 會導致建立 TextShadowNode 物件。值得注意的是,永遠不會有直接代表 <MyComponent>React Shadow 節點

每當 React 在兩個React 元素節點之間建立父子關係時,渲染器也會在對應的React Shadow 節點之間建立相同的關係。這就是 React Shadow 樹的組裝方式。

其他詳細資訊

  • 這些操作(建立React Shadow 節點、在兩個React Shadow 節點之間建立父子關係)是同步且執行緒安全的操作,通常從 React (JavaScript) 執行到渲染器 (C++),通常在 JavaScript 執行緒上。
  • React 元素樹(及其組成的React 元素節點)並非永久存在。它是 React 中「纖維」具體化的暫時表示。每個代表宿主組件的「纖維」都儲存一個 C++ 指標,指向 React Shadow 節點,這是透過 JSI 實現的。在此文件中了解更多關於「纖維」的資訊。
  • React Shadow 樹是不可變的。為了更新任何 React Shadow 節點,渲染器會建立一個新的 React Shadow 樹。但是,渲染器提供了複製操作,以提高狀態更新的效能(請參閱 React 狀態更新 以取得更多詳細資訊)。

在上面的範例中,渲染階段的結果如下所示

Step one

React Shadow 樹完成後,渲染器會觸發 React 元素樹的提交。

階段 2. Commit(提交)

Phase two: commit

提交階段包含兩個操作:版面配置計算樹狀結構提升

  • 版面配置計算: 此操作計算每個 React Shadow 節點的位置和大小。在 React Native 中,這涉及到調用 Yoga 來計算每個 React Shadow 節點的版面配置。實際計算需要每個 React Shadow 節點的樣式,這些樣式源自 JavaScript 中的 React 元素。它還需要 React Shadow 樹根節點的版面配置約束,這決定了結果節點可以佔用的可用空間量。

Step two

  • 樹狀結構提升(新樹 → 下一個樹): 此操作將新的 React Shadow 樹提升為要掛載的「下一個樹」。此提升表示新的 React Shadow 樹具有所有要掛載的資訊,並代表 React 元素樹的最新狀態。「下一個樹」會在 UI 執行緒的下一個「tick」時掛載。

其他詳細資訊

  • 這些操作在背景執行緒上非同步執行。
  • 大多數版面配置計算完全在 C++ 中執行。但是,某些組件的版面配置計算取決於宿主平台(例如 TextTextInput 等)。文字的大小和位置特定於每個宿主平台,需要在宿主平台層計算。為此,Yoga 調用在宿主平台中定義的函數來計算組件的版面配置。

階段 3. Mount(掛載)

Phase three: mount

掛載階段將 React Shadow 樹(現在包含來自版面配置計算的資料)轉換為 Host 視圖樹,並在螢幕上渲染像素。提醒一下,React 元素樹看起來像這樣

jsx
<View>
<Text>Hello, World</Text>
</View>

在高層次上,React Native 渲染器為每個 React Shadow 節點建立對應的 宿主視圖,並將其掛載到螢幕上。在上面的範例中,渲染器為 <View> 建立 android.view.ViewGroup 的實例,為 <Text> 建立 android.widget.TextView,並用「Hello World」填充它。類似地,對於 iOS,會建立一個 UIView,並透過調用 NSLayoutManager 來填充文字。然後,每個宿主視圖都配置為使用來自其 React Shadow 節點的 props,並使用計算出的版面配置資訊配置其大小和位置。

Step two

更詳細地說,掛載階段包含以下三個步驟

  • 樹狀結構差異比較: 此步驟完全在 C++ 中計算「先前渲染的樹」和「下一個樹」之間的差異。結果是要在宿主視圖上執行的原子突變操作列表(例如 createViewupdateViewremoveViewdeleteView 等)。此步驟也是將 React Shadow 樹扁平化以避免建立不必要的宿主視圖的地方。有關此演算法的詳細資訊,請參閱 視圖扁平化
  • 樹狀結構提升(下一個樹 → 渲染的樹):此步驟以原子方式將「下一個樹」提升為「先前渲染的樹」,以便下一個掛載階段針對正確的樹計算差異。
  • 視圖掛載:此步驟將原子突變操作應用到對應的宿主視圖上。此步驟在宿主平台上的 UI 執行緒中執行。

其他詳細資訊

  • 這些操作在 UI 執行緒上同步執行。如果提交階段在背景執行緒上執行,則掛載階段會排程在 UI 執行緒的下一個「tick」時執行。另一方面,如果提交階段在 UI 執行緒上執行,則掛載階段會在同一個執行緒上同步執行。
  • 掛載階段的排程、實作和執行很大程度上取決於宿主平台。例如,目前 Android 和 iOS 之間的掛載層的渲染器架構就不同。
  • 在初始渲染期間,「先前渲染的樹」是空的。因此,樹狀結構差異比較步驟將產生一個突變操作列表,其中僅包含建立視圖、設定 props 以及將視圖彼此新增。當處理 React 狀態更新時,樹狀結構差異比較對於效能變得更加重要。
  • 在目前的生產測試中,一個 React Shadow 樹通常包含約 600-1000 個 React Shadow 節點(在視圖扁平化之前),在視圖扁平化後,樹狀結構會減少到約 200 個節點。在 iPad 或桌上型電腦應用程式上,這個數量可能會增加 10 倍。

React 狀態更新

讓我們探討當 React 元素樹的狀態更新時,渲染管線的每個階段。假設您在初始渲染中渲染了以下組件

jsx
function MyComponent() {
return (
<View>
<View
style={{backgroundColor: 'red', height: 20, width: 20}}
/>
<View
style={{backgroundColor: 'blue', height: 20, width: 20}}
/>
</View>
);
}

應用 初始渲染 區段中描述的內容,您會預期建立以下樹狀結構

Render pipeline 4

請注意,節點 3 對應於具有紅色背景的宿主視圖,而 節點 4 對應於具有藍色背景的宿主視圖。假設由於 JavaScript 產品邏輯中的狀態更新,第一個巢狀 <View> 的背景從 'red' 變更為 'yellow'。這是新的 React 元素樹可能的外觀

jsx
<View>
<View
style={{backgroundColor: 'yellow', height: 20, width: 20}}
/>
<View
style={{backgroundColor: 'blue', height: 20, width: 20}}
/>
</View>

React Native 如何處理此更新?

當狀態更新發生時,渲染器需要概念性地更新 React 元素樹,以便更新已掛載的宿主視圖。但是為了保持執行緒安全,React 元素樹React Shadow 樹都必須是不可變的。這表示 React 必須建立每個樹狀結構的新副本,而不是突變目前的 React 元素樹React Shadow 樹,這些副本包含新的 props、樣式和子項。

讓我們探討狀態更新期間渲染管線的每個階段。

階段 1. Render(渲染)

Phase one: render

當 React 建立包含新狀態的新 React 元素樹時,它必須複製每個受變更影響的 React 元素React Shadow 節點。複製後,新的 React Shadow 樹會被提交。

React Native 渲染器利用結構共享來最大限度地減少不可變性的開銷。當複製 React 元素以包含新狀態時,會複製路徑上直到根節點的每個 React 元素如果 React 元素需要更新其 props、樣式或子項,React 才會複製它。 任何未受狀態更新影響的 React 元素都會在舊樹和新樹之間共享。

在上面的範例中,React 使用以下操作建立新樹

  1. CloneNode(節點 3, {backgroundColor: 'yellow'}) → 節點 3'
  2. CloneNode(節點 2) → 節點 2'
  3. AppendChild(節點 2', 節點 3')
  4. AppendChild(節點 2', 節點 4)
  5. CloneNode(節點 1) → 節點 1'
  6. AppendChild(節點 1', 節點 2')

在這些操作之後,節點 1' 代表新 React 元素樹的根節點。讓我們將 T 分配給「先前渲染的樹」,將 T' 分配給「新樹」

Render pipeline 5

請注意,TT' 都共享 節點 4。結構共享提高了效能並減少了記憶體使用量。

階段 2. Commit(提交)

Phase two: commit

在 React 建立新的 React 元素樹React Shadow 樹之後,它必須提交它們。

  • 版面配置計算: 類似於 初始渲染 期間的版面配置計算。一個重要的區別是,版面配置計算可能會導致共享的 React Shadow 節點被複製。當共享的 React Shadow 節點的父節點發生版面配置變更時,可能會發生這種情況,共享的 React Shadow 節點的版面配置也可能會變更。
  • 樹狀結構提升(新樹 → 下一個樹): 類似於 初始渲染 期間的樹狀結構提升。

階段 3. Mount(掛載)

Phase three: mount

  • 樹狀結構提升(下一個樹 → 渲染的樹):此步驟以原子方式將「下一個樹」提升為「先前渲染的樹」,以便下一個掛載階段針對正確的樹計算差異。
  • 樹狀結構差異比較: 此步驟計算「先前渲染的樹」(T)和「下一個樹」(T')之間的差異。結果是要在宿主視圖上執行的原子突變操作列表。
    • 在上面的範例中,操作包括:UpdateView(**節點 3**, {backgroundColor: 'yellow'})
    • 可以針對任何目前掛載的樹狀結構和任何新樹狀結構計算差異。渲染器可以跳過樹狀結構的一些中間版本。
  • 視圖掛載:此步驟將原子突變操作應用到對應的宿主視圖。在上面的範例中,只會更新 視圖 3backgroundColor(變為黃色)。

Render pipeline 6


React Native 渲染器狀態更新

對於 Shadow 樹 中的大多數資訊,React 是單一所有者和單一事實來源。所有資料都源自 React,並且資料流是單向的。

但是,有一個例外和重要的機制:C++ 中的組件可以包含未直接暴露於 JavaScript 的狀態,而 JavaScript 不是事實來源。C++ 和宿主平台控制這個 C++ 狀態。一般來說,這僅在您開發需要 C++ 狀態的複雜宿主組件時才相關。絕大多數宿主組件不需要此功能。

例如,ScrollView 使用此機制讓渲染器知道目前的偏移量是多少。更新是從宿主平台觸發的,特別是從代表 ScrollView 組件的宿主視圖觸發的。關於偏移量的資訊用於像 measure 這樣的 API 中。由於此更新源自宿主平台,並且不影響 React 元素樹,因此此狀態資料由 C++ 狀態持有。

從概念上講,C++ 狀態更新與上面描述的 React 狀態更新 類似。但有兩個重要的區別

  1. 它們跳過了「渲染階段」,因為 React 沒有參與。
  2. 更新可以源自任何執行緒並在任何執行緒上發生,包括主執行緒。

階段 2. Commit(提交)

Phase two: commit

當執行 C++ 狀態更新時,程式碼區塊會請求更新 ShadowNode (N) 以將 C++ 狀態設定為值 S。React Native 渲染器將重複嘗試取得 N 的最新提交版本,使用新狀態 S 複製它,並將 N' 提交到樹狀結構。如果 React 或另一個 C++ 狀態更新在此期間執行了另一次提交,則 C++ 狀態提交將失敗,並且渲染器將重試 C++ 狀態更新多次,直到提交成功。這可以防止事實來源衝突和競爭。

階段 3. Mount(掛載)

Phase three: mount

掛載階段實際上與 React 狀態更新的掛載階段 相同。渲染器仍然需要重新計算版面配置、執行樹狀結構差異比較等。有關詳細資訊,請參閱上面的章節。