React Native 新架構是如何工作的?
目前 React Native 新架構所依賴的 React 18 已經發了 beta 版,React Native 新架構面向生態庫和核心開發者的文檔也正式發佈,React Native 團隊成員 Kevin Gozali 也在最近一次訪談中談到新架構離正式發版還差最後一步延遲初始化,而最後一步大約會在 2022 年上半年完成。種種跡象表明,React Native 新架構真的要來了。
後續也會通過極客時間專欄的形式和大家介紹新架構的使用方法、剖析架構原理、講解實踐方案。
由於時間倉促,如果有翻譯不當之處還請大家指出,以下是正文部分。
本文檔還在更新持續中,會從概念上介紹 React Native 新架構是如何工作的。目標讀者包括生態庫的開發者、核心貢獻者和特別有好奇心的人。文檔介紹了即將發佈的新渲染器 Fabric 的架構。
Fabric
Fabric 是 React Native 新架構的渲染系統,是從老架構的渲染系統演變而來的。核心原理是在 C++ 層統一更多的渲染邏輯,提升與宿主平臺(host platforms)互操作性,併爲 React Native 解鎖更多能力。研發始於 2018 年和 2021 年,Facebook 應用中的 React Native 用的就是新的渲染器。
該文檔簡介了新渲染器(new renderer)及其核心概念,它不包括平臺細節和任何代碼細節,它介紹了核心概念、初衷、收益和不同場景的渲染流程。
名詞解釋:
宿主平臺(Host platform):React Native 嵌入的平臺,比如 Android、iOS、Windows、macOS。
Fabric 渲染器(Fabric Renderer):React Native 執行的 React 框架代碼,和 React 在 Web 中執行代碼是同一份。但是,React Native 渲染的是通用平臺視圖(宿主視圖)而不是 DOM 節點(可以認爲 DOM 是 Web 的宿主視圖)。Fabric 渲染器使得渲染宿主視圖變得可行。Fabric 讓 React 與各個平臺直接通信並管理其宿主視圖實例。Fabric 渲染器存在於 JavaScript 中,並且它調用的是由 C++ 代碼暴露的接口。在這篇文章中有更多關於 React 渲染器的信息。
新渲染器的初衷和收益
開發新的渲染架構的初衷是爲了更好的用戶體驗,而這種新體驗是在老架構上是不可能實現的。比如:
-
爲了提升宿主視圖(host views)和 React 視圖(React views)的互操作性,渲染器必須有能力同步地測量和渲染 React 界面。在老架構中,React Native 佈局是異步的,這導致在宿主視圖中渲染嵌套的 React Native 視圖,會有佈局 “抖動” 的問題。
-
藉助多優先級和同步事件的能力,渲染器可以提高用戶交互的優先級,來確保他們的操作得到及時的處理。
-
React Suspense 的集成,允許你在 React 中更符合直覺地寫請求數據代碼。
-
允許你在 React Native 使用 React Concurrent 可中斷渲染功能。
-
更容易實現 React Native 的服務端渲染。
新架構的收益還包括,代碼質量、性能、可擴展性。
-
類型安全:代碼生成工具(code generation)確保了 JS 和宿主平臺兩方面的類型安全。代碼生成工具使用 JavaScript 組件聲明作爲唯一事實源,生成 C++ 結構體來持有 props 屬性。不會因爲 JavaScript 和宿主組件 props 屬性不匹配而出現構建錯誤。
-
共享 C++ core:渲染器是用 C++ 實現的,其核心 core 在平臺之間是共享的。這增加了一致性並且使得新的平臺能夠更容易採用 React Native。(譯註:例如 VR 新平臺)
-
更好的宿主平臺互操作性:當宿主組件集成到 React Native 時,同步和線程安全的佈局計算提升了用戶體驗(譯註:沒有異步的抖動)。這意味着那些需要同步 API 的宿主平臺庫,變得更容易集成了。
-
性能提升:新的渲染系統的實現是跨平臺的,每個平臺都從那些原本只在某個特定平臺的實現的性能優化中,得到了收益。比如拍平視圖層級,原本只是 Android 上的性能優化方案,現在 Android 和 iOS 都直接有了。
-
一致性:新的渲染系統的實現是跨平臺的,不同平臺之間更容易保持一致。
-
更快的啓動速度:默認情況下,宿主組件的初始化是懶執行的。
-
JS 和宿主平臺之間的數據序列化更少:React 使用序列化 JSON 在 JavaScript 和宿主平臺之間傳遞數據。新的渲染器用 JSI(JavaScript Interface)直接獲取 JavaScript 數據。
名詞解釋
JavaScript Interfaces (JSI):一個輕量級的 API,給在 C++ 應用中嵌入的 JavaScript 引擎用的。Fabric 使用它在 Fabric 的 C++ 核心和 React 之間進行通信。
渲染、提交和掛載
React Native 渲染器通過一系列加工處理,將 React 代碼渲染到宿主平臺。這一系列加工處理就是渲染流水線(pipeline),它的作用是初始化渲染和 UI 狀態更新。接下來介紹的是渲染流水線,及其在各種場景中的不同之處。
(譯註:pipeline 的原義是將計算機指令處理過程拆分爲多個步驟,並通過多個硬件處理單元並行執行來加快指令執行速度。其具體執行過程類似工廠中的流水線,並因此得名。)
渲染流水線可大致分爲三個階段:
-
渲染(Render):在 JavaScript 中,React 執行那些產品邏輯代碼創建 React 元素樹(React Element Trees)。然後在 C++ 中,用 React 元素樹創建 React 影子樹(React Shadow Tree)。
-
提交(Commit):在 React 影子樹完全創建後,渲染器會觸發一次提交。這會將 React 元素樹和新創建的 React 影子樹的提升爲 “下一棵要掛載的樹”。這個過程中也包括了佈局信息計算。
-
掛載(Mount):React 影子樹有了佈局計算結果後,它會被轉化爲一個宿主視圖樹(Host View Tree)。
名詞解釋
React 元素樹(React Element Trees):React 元素樹是通過 JavaScript 中的 React 創建的,該樹由一系類 React 元素組成。一個 React 元素就是一個普通的 JavaScript 對象,它描述了應該在屏幕中展示什麼。一個元素包括屬性 props、樣式 styles、子元素 children。React 元素分爲兩類:React 複合組件實例(React Composite Components)和 React 宿主組件(React Host Components)實例,並且它只存在於 JavaScript 中。
React 影子樹(React Shadow Tree):React 影子樹是通過 Fabric 渲染器創建的,樹由一系列 React 影子節點組成。一個 React 影子節點是一個對象,代表一個已經掛載的 React 宿主組件,其包含的屬性 props 來自 JavaScript。它也包括佈局信息,比如座標系 x、y,寬高 width、height。在新渲染器 Fabric 中,React 影子節點對象只存在於 C++ 中。而在老架構中,它存在於手機運行時的堆棧中,比如 Android 的 JVM。
宿主視圖樹(Host View Tree):宿主視圖樹就是一系列的宿主視圖。宿主平臺有 Android 平臺、iOS 平臺等等。在 Android 上,宿主視圖就是
android.view.ViewGroup
實例、android.widget.TextView
實例等等。宿主視圖就像積木一樣地構成了宿主視圖樹。每個宿主視圖的大小和座標位置基於的是LayoutMetrics
,而LayoutMetrics
是通過佈局引擎 Yoga 計算出來的。宿主視圖的樣式和內容信息,是從 React 影子樹中得到的。渲染流水線的各個階段可能發生在不同的線程中,更詳細的信息可以參考線程模型部分。
渲染流水線存在三種不同場景:
-
初始化渲染
-
React 狀態更新
-
React Native 渲染器的狀態更新
初始化渲染
渲染階段
想象一下你準備渲染一個組件:
function MyComponent() {
return (
<View>
<Text>Hello, World</Text>
</View>
);
}
// <MyComponent />
在上面的例子中,<MyComponent />
是 React 元素。React 會將 React 元素簡化爲最終的 React 宿主組件。每一次都會遞歸地調用函數組件 MyComponet ,或類組件的 render 方法,直至所有的組件都被調用過。現在,你擁有一棵 React 宿主組件的 React 元素樹。
名詞解釋:
React 組件(React Component):React 組件就是 JavaScript 函數或者類,描述如何創建 React 元素。
React 複合組件(React Composite Components):React 組件的 render 方法中,包括其他 React 複合組件和 React 宿主組件。(譯註:複合組件就是開發者聲明的組件)
React 宿主組件(React Host Components):React 組件的視圖是通過宿主視圖,比如
<View>
、<Text>
,實現的。在 Web 中,ReactDOM 的宿主組件就是<p>
標籤、<div>
標籤代表的組件。
在元素簡化的過程中,每調用一個 React 元素,渲染器同時會同步地創建 React 影子節點。這個過程只發生在 React 宿主組件上,不會發生在 React 複合組件上。比如,一個 <View>
會創建一個 ViewShadowNode
對象,一個<Text>
會創建一個TextShadowNode
對象。注意,<MyComponent>
並沒有直接對應的 React 影子節點。
在 React 爲兩個 React 元素節點創建一對父子關係的同時,渲染器也會爲對應的 React 影子節點創建一樣的父子關係。這就是影子節點的組裝方式。
其他細節
-
創建 React 影子節點、創建兩個影子節點的父子關係的操作是同步的,也是線程安全的。該操作的執行是從 React(JavaScript)到渲染器(C++)的,大部分情況下是在 JavaScript 線程上執行的。(譯註:後面線程模型有解釋)
-
React 元素樹和元素樹中的元素並不是一直存在的,它只一個當前視圖的描述,而最終是由 React “fiber” 來實現的。每一個 “fiber” 都代表一個宿主組件,存着一個 C++ 指針,指向 React 影子節點。這些都是因爲有了 JSI 纔有可能實現的。學習更多關於 “fibers” 的資料參考 這篇文檔。
-
React 影子樹是不可變的。爲了更新任意的 React 影子節點,渲染器會創建了一棵新的 React 影子樹。爲了讓狀態更新更高效,渲染器提供了 clone 操作。更多細節可參考後面的 React 狀態更新部分。
在上面的示例中,各個渲染階段的產物如圖所示:
提交階段
在 React 影子樹創建完成後,渲染器觸發了一次 React 元素樹的提交。
提交階段(Commit Phase)由兩個操作組成:佈局計算和樹的提升。
- 佈局計算(Layout Calculation):這一步會計算每個 React 影子節點的位置和大小。在 React Native 中,每一個 React 影子節點的佈局都是通過 Yoga 佈局引擎來計算的。實際的計算需要考慮每一個 React 影子節點的樣式,該樣式來自於 JavaScript 中的 React 元素。計算還需要考慮 React 影子樹的根節點的佈局約束,這決定了最終節點能夠擁有多少可用空間。
- 樹提升,從新樹到下一棵樹(Tree Promotion,New Tree → Next Tree):這一步會將新的 React 影子樹提升爲要掛載的下一棵樹。這次提升代表着新樹擁有了所有要掛載的信息,並且能夠代表 React 元素樹的最新狀態。下一棵樹會在 UI 線程下一個 “tick” 進行掛載。(譯註:tick 是 CUP 的最小時間單元)
更多細節
-
這些操作都是在後臺線程中異步執行的。
-
絕大多數佈局計算都是 C++ 中執行,只有某些組件,比如 Text、TextInput 組件等等,的佈局計算是在宿主平臺執行的。文字的大小和位置在每個宿主平臺都是特別的,需要在宿主平臺層進行計算。爲此,Yoga 佈局引擎調用了宿主平臺的函數來計算這些組件的佈局。
掛載階段
掛載階段(Mount Phase)會將已經包含佈局計算數據的 React 影子樹,轉換爲以像素形式渲染在屏幕中的宿主視圖樹。請記住,這棵 React 元素樹看起來是這樣的:
<View>
<Text>Hello, World</Text>
</View>
站在更高的抽象層次上,React Native 渲染器爲每個 React 影子節點創建了對應的宿主視圖,並且將它們掛載在屏幕上。在上面的例子中,渲染器爲<View>
創建了android.view.ViewGroup
實例,爲 <Text>
創建了文字內容爲 “Hello World” 的 android.widget.TextView
實例 。iOS 也是類似的,創建了一個 UIView
並調用 NSLayoutManager
創建文本。然後會爲宿主視圖配置來自 React 影子節點上的屬性,這些宿主視圖的大小位置都是通過計算好的佈局信息配置的。
更詳細地說,掛載階段由三個步驟組成:
-
樹對比(Tree Diffing): 這個步驟完全用的是 C++ 計算的,會對比 “已經渲染的樹”(previously rendered tree)和” 下一棵樹”之間的差異。計算的結果是一系列宿主平臺上的原子變更操作,比如
createView
,updateView
,removeView
,deleteView
等等。在這個步驟中,還會將 React 影子樹拍平,來避免不必要的宿主視圖創建。關於視圖拍平的算法細節可以在後文找到。 -
樹提升,從下一棵樹到已渲染樹(Tree Promotion,Next Tree → Rendered Tree): 在這個步驟中,會自動將 “下一棵樹” 提升爲“先前渲染的樹”,因此在下一個掛載階段,樹的對比計算用的是正確的樹。
-
視圖掛載(View Mounting): 這個步驟會在對應的原生視圖上執行原子變更操作,該步驟是發生在原生平臺的 UI 線程的。
更多細節
-
掛載階段的所有操作都是在 UI 線程同步執行的。如果提交階段是在後臺線程執行,那麼在掛載階段會在 UI 線程的下一個 “tick” 執行。另外,如果提交階段是在 UI 線程執行的,那麼掛載階段也是在 UI 線程執行。
-
掛載階段的調度和執行很大程度取決於宿主平臺。例如,當前 Android 和 iOS 掛載層的渲染架構是不一樣的。
-
在初始化渲染時,“先前渲染的樹” 是空的。因此,樹對比(tree diffing)步驟只會生成一系列僅包含創建視圖、設置屬性、添加視圖的變更操作。而在接下來的 React 狀態更新場景中,樹對比的性能至關重要。
-
在當前生產環境的測試中,在視圖拍平之前,React 影子樹通常由大約 600-1000 個 React 影子節點組成。在視圖拍平之後,樹的節點數量會減少到大約 200 個。在 iPad 或桌面應用程序上,這個節點數量可能要乘個 10。
React 狀態更新
接下來,我們繼續看 React 狀態更新時,渲染流水線(render pipeline)的各個階段是什麼樣的。假設你在初始化渲染時,渲染的是如下組件:
function MyComponent() {
return (
<View>
<View
style={{ backgroundColor: 'red', height: 20, width: 20 }}
/>
<View
style={{ backgroundColor: 'blue', height: 20, width: 20 }}
/>
</View>
);
}
應用我們在初始化渲染部分學的知識,你可以得到如下的三棵樹:
請注意,節點 3 對應的宿主視圖背景是 紅的,而 節點 4 對應的宿主視圖背景是 藍的。假設 JavaScript 的產品邏輯是,將第一個內嵌的<View>
的背景顏色由紅色改爲黃色。新的 React 元素樹看起來大概是這樣:
<View>
<View
style={{ backgroundColor: 'yellow', height: 20, width: 20 }}
/>
<View
style={{ backgroundColor: 'blue', height: 20, width: 20 }}
/>
</View>
React Native 是如何處理這個更新的?
從概念上講,當發生狀態更新時,爲了更新已經掛載的宿主視圖,渲染器需要直接更新 React 元素樹。但是爲了線程的安全,React 元素樹和 React 影子樹都必須是不可變的(immutable)。這意味着 React 並不能直接改變當前的 React 元素樹和 React 影子樹,而是必須爲每棵樹創建一個包含新屬性、新樣式和新子節點的新副本。
讓我們繼續探究狀態更新時,渲染流水線的各個階段發生了什麼。
渲染階段
React 要創建了一個包含新狀態的新的 React 元素樹,它就要複製所有變更的 React 元素和 React 影子節點。複製後,再提交新的 React 元素樹。
React Native 渲染器利用結構共享的方式,將不可變特性的開銷變得最小。爲了更新 React 元素的新狀態,從該元素到根元素路徑上的所有元素都需要複製。但 React 只會複製有新屬性、新樣式或新子元素的 React 元素,任何沒有因狀態更新發生變動的 React 元素都不會複製,而是由新樹和舊樹共享。
在上面的例子中,React 創建新樹使用了這些操作:
-
CloneNode(Node 3, {backgroundColor: 'yellow'}) → Node 3'
-
CloneNode(Node 2) → Node 2'
-
AppendChild(Node 2', Node 3')
-
AppendChild(Node 2', Node 4)
-
CloneNode(Node 1) → Node 1'
-
AppendChild(Node 1', Node 2')
操作完成後,節點 1'(Node 1') 就是新的 React 元素樹的根節點。我們用 T 代表 “先前渲染的樹”,用 T' 代表 “新樹”。
注意節點 4 在 T and T' 之間是共享的。結構共享提升了性能並減少了內存的使用。
提交階段
在 React 創建完新的 React 元素樹和 React 影子樹後,需要提交它們。
-
佈局計算(Layout Calculation): 狀態更新時的佈局計算,和初始化渲染的佈局計算類似。一個重要的不同之處是佈局計算可能會導致共享的 React 影子節點被複制。這是因爲,如果共享的 React 影子節點的父節點引起了佈局改變,共享的 React 影子節點的佈局也可能發生改變。
-
樹提升(Tree Promotion ,New Tree → Next Tree): 和初始化渲染的樹提升類似。
-
樹對比(Tree Diffing): 這個步驟會計算 “先前渲染的樹”(T)和 “下一棵樹”(T')的區別。計算的結果是原生視圖的變更操作。
-
在上面的例子中,這些操作包括:
UpdateView(**'Node 3'**, {backgroundColor: 'yellow'})
掛載階段
-
樹提升(Tree Promotion ,Next Tree → Rendered Tree): 在這個步驟中,會自動將 “下一棵樹” 提升爲“先前渲染的樹”,因此在下一個掛載階段,樹的對比計算用的是正確的樹。
-
視圖掛載(View Mounting): 這個步驟會在對應的原生視圖上執行原子變更操作。在上面的例子中,只有 視圖 3(View 3) 的背景顏色會更新,變爲黃色。
React Native 渲染器狀態更新
對於影子樹中的大多數信息而言,React 是唯一所有方也是唯一事實源。並且所有來源於 React 的數據都是單向流動的。
但有一個例外。這個例外是一種非常重要的機制:C++ 組件可以擁有狀態,且該狀態可以不直接暴露給 JavaScript,這時候 JavaScript (或 React)就不是唯一事實源了。通常,只有複雜的宿主組件纔會用到 C++ 狀態,絕大多數宿主組件都不需要此功能。
例如,ScrollView 使用這種機制讓渲染器知道當前的偏移量是多少。偏移量的更新是宿主平臺的觸發,具體地說是 ScrollView 組件。這些偏移量信息在 React Native 的 measure 等 API 中有用到。因爲偏移量數據是由 C++ 狀態持有的,所以源於宿主平臺更新,不影響 React 元素樹。
從概念上講,C++ 狀態更新類似於我們前面提到的 React 狀態更新,但有兩點不同:
-
因爲不涉及 React,所以跳過了 “渲染階段”(Render phase)。
-
更新可以源自和發生在任何線程,包括主線程。
提交階段(Commit Phase):在執行 C++ 狀態更新時,會有一段代碼把影子節點 (N) 的 C++ 狀態設置爲值 S。React Native 渲染器會反覆嘗試獲取 N 的最新提交版本,並使用新狀態 S 複製它 ,並將新的影子節點 N' 提交給影子樹。如果 React 在此期間執行了另一次提交,或者其他 C++ 狀態有了更新,本次 C++ 狀態提交失敗。這時渲染器將多次重試 C++ 狀態更新,直到提交成功。這可以防止真實源的衝突和競爭。
掛載階段(Mount Phase)實際上與 React 狀態更新的掛載階段相同。渲染器仍然需要重新計算佈局、執行樹對比等操作。詳細步驟在前面已經講過了。
跨平臺實現
React Native 渲染器使用 C++ core 渲染實現了跨平臺共享。
在上一代 React Native 渲染器中,React 影子樹、佈局邏輯、視圖拍平算法是在各個平臺單獨實現的。當前的渲染器的設計上採用的是跨平臺的解決方案,共享了核心的 C++ 實現。
React Native 團隊計劃將動畫系統加入到渲染系統中,並將 React Native 的渲染系統擴展到新的平臺,例如 Windows、遊戲機、電視等等。
使用 C++ 作爲核心渲染系統有幾個有點。首先,單一實現降低了開發和維護成本。其次,它提升了創建 React 影子樹的性能,同時在 Android 上,也因爲不再使用 JNI for Yoga,降低了 Yoga 渲染引擎的開銷,佈局計算的性能也有所提升。最後,每個 React 影子節點在 C++ 中佔用的內存,比在 Kotlin 或 Swift 中佔用的要小。
名詞解釋
Java Native Interface (JNI):一個用 Java 寫的 API,用於在 Java 中寫 native(譯註:指調用 C++) 方法。作用是實現 Fabric 的 C++ 核心和 Android 的通信。
React Native 團隊還使用了強制不可變的 C++ 特性,來確保併發訪問時共享資源即便不加鎖保護,也不會有問題。
但在 Android 端還有兩種例外,渲染器依然會有 JNI 的開銷:
-
複雜視圖,比如 Text、TextInput 等,依然會使用 JNI 來傳輸屬性 props。
-
在掛載階段依然會使用 JNI 來發送變更操作。
React Native 團隊在探索使用 ByteBuffer
序列化數據這種新的機制,來替換 ReadableMap
,減少 JNI 的開銷。目標是將 JNI 的開銷減少 35~50%。
渲染器提供了 C++ 與兩邊通信的 API:
-
(i) 與 React 通信
-
(ii) 與宿主平臺通信
關於 **(i)**React 與渲染器的通信,包括 渲染(render) React 樹和監聽 事件(event),比如 onLayout
、onKeyPress
、touch 等。
關於 (ii) React Native 渲染器與宿主平臺的通信,包括在屏幕上 掛載(mount) 宿主視圖,包括 create、insert、update、delete 宿主視圖,和監聽用戶在宿主平臺產生的 事件(event)。
視圖拍平
視圖拍平(View Flattening)是 React Native 渲染器避免佈局嵌套太深的優化手段。
React API 在設計上希望通過組合的方式,實現組件聲明和重用,這爲更簡單的開發提供了一個很好的模型。但是在實現中,API 的這些特性會導致一些 React 元素會嵌套地很深,而其中大部分 React 元素節點只會影響視圖佈局,並不會在屏幕中渲染任何內容。這就是所謂的 “只參與佈局” 類型節點。
從概念上講,React 元素樹的節點數量和屏幕上的視圖數量應該是 1:1 的關係。但是,渲染一個很深的 “只參與佈局” 的 React 元素會導致性能變慢。
舉個很常見的例子,例子中 “只參與佈局” 視圖導致了性能損耗。
想象一下,你要渲染一個標題。你有一個應用,應用中擁有外邊距 ContainerComponent
的容器組件,容器組件的子組件是 TitleComponent
標題組件,標題組件包括一個圖片和一行文字。React 代碼示例如下:
function MyComponent() {
return (
<View> // ReactAppComponent
<View style={{margin: 10}} /> // ContainerComponent
<View style={{margin: 10}}> // TitleComponent
<Image {...} />
<Text {...}>This is a title</Text>
</View>
</View>
</View>
);
}
React Native 在渲染時,會生成以下三棵樹:
注意視圖 2 和視圖 3 是 “只參與佈局” 的視圖,因爲它們在屏幕上渲染只是爲了提供 10 像素的外邊距。
爲了提升 React 元素樹中 “只參與佈局” 類型的性能,渲染器實現了一種視圖拍平的機制來合併或拍平這類節點,減少屏幕中宿主視圖的層級深度。該算法考慮到了如下屬性,比如 margin
, padding
, backgroundColor
, opacity
等等。
視圖拍平算法是渲染器的對比(diffing)階段的一部分,這樣設計的好處是我們不需要額外的 CUP 耗時,來拍平 React 元素樹中 “只參與佈局” 的視圖。此外,作爲 C++ 核心的一部分,視圖拍平算法默認是全平臺共用的。
在前面的例子中,視圖 2 和視圖 3 會作爲 “對比算法”(diffing algorithm)的一部分被拍平,而它們的樣式結果會被合併到視圖 1 中。
雖然,這種優化讓渲染器少創建和渲染兩個宿主視圖,但從用戶的角度看屏幕內容沒有任何區別。
線程模型
React Native 渲染器在多個線程之間分配渲染流水線(render pipeline)任務。
接下來我們會給線程模型下定義,並提供一些示例來說明渲染流水線的線程用法。
React Native 渲染器是線程安全的。從更高的視角看,在框架內部線程安全是通過不可變的數據結果保障的,其使用的是 C++ 的 const correctness 特性。這意味着,在渲染器中 React 的每次更新都會重新創建或複製新對象,而不是更新原有的數據結構。這是框架把線程安全和同步 API 暴露給 React 的前提。
渲染器使用三個不同的線程:
-
UI 線程(主線程):唯一可以操作宿主視圖的線程。
-
JavaScript 線程:這是執行 React 渲染階段的地方。
-
後臺線程:專門用於佈局的線程。
讓我們回顧一下每個階段支持的執行場景:
渲染場景
在後臺線程中渲染
這是最常見的場景,大多數的渲染流水線發生在 JavaScript 線程和後臺線程。
在主線程中渲染
當 UI 線程上有高優先級事件時,渲染器能夠在 UI 線程上同步執行所有渲染流水線。
默認或連續事件中斷
在這個場景中,UI 線程的低優先級事件中斷了渲染步驟。React 和 React Native 渲染器能夠中斷渲染步驟,並把它的狀態和一個在 UI 線程執行的低優先級事件合併。在這個例子中渲染過程會繼續在後臺線程中執行。
不相干的事件中斷
渲染步驟是可中斷的。在這個場景中, UI 線程的高優先級事件中斷了渲染步驟。React 和渲染器是能夠打斷渲染步驟的,並把它的狀態和 UI 線程執行的高優先級事件合併。在 UI 線程渲染步驟是同步執行的。
來自 JavaScript 線程的後臺線程批量更新
在後臺線程將更新分派給 UI 線程之前,它會檢查是否有新的更新來自 JavaScript。這樣,當渲染器知道新的狀態要到來時,它就不會直接渲染舊的狀態。
C++ 狀態更新
更新來自 UI 線程,並會跳過渲染步驟。更多細節請參考 React Native 渲染器狀態更新。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/fROPFAHpiiXXe56H05ubVw