跳到主要內容

全新架構登場

·22 分鐘閱讀時間
The React Team
React 團隊
@reactjs / @reactnative

預設採用全新架構的 React Native 0.76 現在已在 npm 上推出!

0.76 版本發布部落格文章中,我們分享了此版本中包含的重大變更清單。在這篇文章中,我們將概述全新架構及其如何塑造 React Native 的未來。

全新架構新增了對現代 React 功能的完整支援,包括 SuspenseTransitions自動批次處理,以及 useLayoutEffect。全新架構也包含全新的 原生模組原生組件系統,讓您能編寫型別安全程式碼,並直接存取原生介面,而無需橋接器。

此版本是我們自 2018 年以來一直致力於 React Native 的從頭重寫的成果,我們格外謹慎,讓全新架構成為大多數應用程式的漸進式遷移。在 2021 年,我們建立了 全新架構工作小組,與社群合作,確保整個 React 生態系統都能獲得順暢的升級體驗。

大多數應用程式都能以與其他任何版本相同的精力採用 React Native 0.76。最熱門的 React Native 函式庫已支援全新架構。全新架構也包含自動互通性層,以啟用與以舊架構為目標的函式庫的回溯相容性。

在過去幾年的開發中,我們的團隊已公開分享我們對全新架構的願景。如果您錯過了任何這些演講,請在此處查看

什麼是全新架構

全新架構是對 React Native 基礎主要系統的完整重寫,包括組件的呈現方式、JavaScript 抽象化如何與原生抽象化通訊,以及如何在不同執行緒之間排程工作。雖然大多數使用者不應考慮這些系統的運作方式,但這些變更帶來了改進和新功能。

在舊架構中,React Native 使用非同步橋接器與原生平台通訊。為了呈現組件或呼叫原生函式,React Native 需要使用橋接器序列化原生函式呼叫並將其加入佇列,這將會以非同步方式處理。此架構的優點是主執行緒永遠不會因為呈現更新或處理原生模組函式呼叫而遭到封鎖,因為所有工作都在背景執行緒上完成。

然而,使用者期望互動能立即回饋,以感覺像原生應用程式。這表示某些更新需要同步呈現,以回應使用者輸入,可能會中斷任何進行中的呈現。由於舊架構僅為非同步,我們需要重寫它,以允許非同步和同步更新。

此外,在舊架構中,透過橋接器序列化函式呼叫很快就成為瓶頸,尤其是在頻繁更新或大型物件的情況下。這使得應用程式難以可靠地達到 60+ FPS。也存在同步問題:當 JavaScript 和原生層失去同步時,無法同步協調它們,導致錯誤,例如清單顯示空白空間的影格,以及由於中間狀態呈現而導致的視覺 UI 跳動。

最後,由於舊架構使用原生階層結構保留 UI 的單一副本,並就地變更該副本,因此版面配置只能在單一執行緒上計算。這使得無法處理緊急更新 (例如使用者輸入),並且無法同步讀取版面配置,例如在版面配置效果中讀取以更新工具提示的位置。

所有這些問題都表示無法正確支援 React 的並行功能。為了解決這些問題,全新架構包含四個主要部分

  • 全新原生模組系統
  • 全新渲染器
  • 事件迴圈
  • 移除橋接器

全新模組系統允許 React Native 渲染器同步存取原生層,使其能夠處理事件、排程更新,以及非同步和同步讀取版面配置。新的原生模組預設也會延遲載入,讓應用程式獲得顯著的效能提升。

全新渲染器可以處理跨多個執行緒的多個進行中樹狀結構,這讓 React 能夠處理多個並行更新優先順序,無論是在主執行緒還是背景執行緒上。它也支援從多個執行緒同步或非同步讀取版面配置,以支援更靈敏的 UI,而不會產生卡頓。

新的事件迴圈可以依明確定義的順序處理 JavaScript 執行緒上的工作。這讓 React 能夠中斷呈現以處理事件,以便緊急使用者事件可以優先於較低優先順序的 UI 轉換。事件迴圈也符合 Web 規格,因此我們可以支援瀏覽器功能,例如微任務、MutationObserverIntersectionObserver

最後,移除橋接器可加快啟動速度,並在 JavaScript 和原生執行階段之間建立直接通訊,從而最大限度地降低切換工作的成本。這也有助於改善錯誤報告、偵錯,並減少因未定義行為而導致的當機。

全新架構現在已準備好在生產環境中使用。它已在 Meta 的 Facebook 應用程式和其他產品中大規模使用。我們在為 Quest 裝置開發的 Facebook 和 Instagram 應用程式中成功使用了 React Native 和全新架構。

我們的合作夥伴已在生產環境中使用全新架構數月:請查看 ExpensifyKraken 的這些成功案例,並在 BlueSky 的新版本中試用 BlueSky

全新原生模組

新的原生模組系統是對 JavaScript 和原生平台如何通訊的重大重寫。它完全以 C++ 編寫,解鎖了許多新功能

  • 從原生執行階段同步存取和從原生執行階段同步存取
  • JavaScript 和原生程式碼之間的型別安全
  • 跨平台程式碼共用
  • 預設延遲模組載入

在新的原生模組系統中,JavaScript 和原生層現在可以透過 JavaScript 介面 (JSI) 彼此同步通訊,而無需使用非同步橋接器。這表示您的自訂原生模組現在可以同步呼叫函式、傳回值,並將該值傳回另一個原生模組函式。

在舊架構中,為了處理來自原生函式呼叫的回應,您需要提供回呼,且傳回的值需要可序列化

// ❌ Sync callback from Native Module
nativeModule.getValue(value => {
// ❌ value cannot reference a native object
nativeModule.doSomething(value);
});

在全新架構中,您可以同步呼叫原生函式

// ✅ Sync response from Native Module
const value = nativeModule.getValue();

// ✅ value can be a reference to a native object
nativeModule.doSomething(value);

透過全新架構,您最終可以充分利用 C++ 原生實作的強大功能,同時仍然可以從 JavaScript/TypeScript API 存取它。新的模組系統支援 以 C++ 編寫的模組,因此您可以編寫一次模組,它就可以在所有平台上運作,包括 Android、iOS、Windows 和 macOS。以 C++ 實作模組允許更精細的記憶體管理和效能最佳化。

此外,透過 Codegen,您的模組可以在 JavaScript 層和原生層之間定義強型別合約。根據我們的經驗,跨邊界型別錯誤是跨平台應用程式中最常見的當機來源之一。Codegen 可讓您克服這些問題,同時也為您產生樣板程式碼。

最後,模組現在會延遲載入:它們僅在實際需要時 (而不是在啟動時) 載入記憶體。這縮短了應用程式啟動時間,並在應用程式複雜性增加時保持較低的啟動時間。

熱門函式庫 (例如 react-native-mmkv) 已從遷移到新的原生模組中看到好處

「新的原生模組大幅簡化了 react-native-mmkv 的設定、自動連結和初始化。由於全新架構,react-native-mmkv 現在是一個純 C++ 原生模組,這使其可以在任何平台上運作。新的 Codegen 允許 MMKV 完全型別安全,這透過強制執行空值安全修復了長期存在的 NullPointerReference 問題,並且能夠同步呼叫原生模組函式讓我們能夠使用新的原生模組 API 取代自訂 JSI 存取。」

Marc Rousavyreact-native-mmkv 的建立者

全新渲染器

我們也完全重寫了原生渲染器,新增了多項優點

  • 更新可以在不同執行緒上以不同優先順序呈現。
  • 版面配置可以同步且跨不同執行緒讀取。
  • 渲染器以 C++ 編寫,並在所有平台上共用。

更新後的原生渲染器現在以不可變的樹狀結構儲存檢視階層結構。這表示 UI 的儲存方式無法直接變更,允許安全執行緒處理更新。這使其能夠處理多個進行中樹狀結構,每個樹狀結構代表不同版本的使用者介面。因此,更新可以在背景中呈現,而不會封鎖 UI (例如在轉換期間) 或在主執行緒上呈現 (以回應使用者輸入)。

透過支援多個執行緒,React 可以中斷低優先順序更新以呈現緊急更新 (例如使用者輸入產生的更新),然後在需要時繼續低優先順序更新。新的渲染器也可以同步且跨不同執行緒讀取版面配置資訊。這可以在需要時啟用低優先順序更新的背景計算和同步讀取,例如重新定位工具提示。

最後,以 C++ 重寫渲染器使其能夠在所有平台上共用。這確保相同的程式碼可以在 iOS、Android、Windows、macOS 和任何其他 React Native 支援的平台上執行,提供一致的呈現功能,而無需為每個平台重新實作。

這是朝向我們的 多平台願景邁出的重要一步。例如,「檢視平面化」是僅限 Android 的最佳化,可避免深度版面配置樹狀結構。新的渲染器具有共用的 C++ 核心,將此功能帶到 iOS。此最佳化是自動的,不需要設定,它與共用渲染器免費提供。

透過這些變更,React Native 現在完全支援並行 React 功能 (例如 Suspense 和 Transitions),讓您更輕鬆地建置複雜的使用者介面,以快速回應使用者輸入,而不會產生卡頓、延遲或視覺跳動。未來,我們將利用這些新功能,為 FlatList 和 TextInput 等內建組件帶來更多改進。

熱門函式庫 (例如 Reanimated) 已在利用全新渲染器

「目前正在開發中的 Reanimated 4 導入了一個新的動畫引擎,可直接與新的渲染器搭配運作,使其能夠處理動畫並跨不同執行緒管理版面配置。新的渲染器設計真正讓這些功能的建置成為可能,而無需依賴大量變通方法。此外,由於它是以 C++ 實作並跨平台共用,因此可以編寫一次 Reanimated 的大部分程式碼,從而減少平台特定的問題、最大限度地減少程式碼庫,並簡化樹狀結構外平台的採用。」

Krzysztof MagieraReanimated 的建立者

事件迴圈

全新架構讓我們能夠實作明確定義的事件迴圈處理模型,如此 RFC 中所述。此 RFC 遵循 HTML 標準中描述的規格,並且描述了 React Native 應如何在 JavaScript 執行緒上執行工作。

實作明確定義的事件迴圈縮小了 React DOM 和 React Native 之間的差距:React Native 應用程式的行為現在更接近 React DOM 應用程式的行為,使其更容易學習一次,隨處編寫。

事件迴圈為 React Native 帶來許多好處

  • 中斷呈現以處理事件和工作的能力
  • 更符合 Web 規格
  • 更多瀏覽器功能的基礎

透過事件迴圈,React 能夠可預測地排序更新和事件。這讓 React 能夠使用緊急使用者事件中斷低優先順序更新,而新的渲染器讓我們能夠獨立呈現這些更新。

事件迴圈也使事件和工作 (例如計時器) 的行為與 Web 規格對齊,這表示 React Native 的運作方式更像使用者在 Web 中熟悉的方式,並允許在 React DOM 和 React Native 之間更好地共用程式碼。

它也允許實作更符合規範的瀏覽器功能,例如微任務、MutationObserverIntersectionObserver。這些功能尚未準備好在 React Native 中使用,但我們正在努力在未來將它們帶給您。

最後,事件迴圈和新的渲染器變更以支援同步讀取版面配置,讓 React Native 能夠為 useLayoutEffect 新增適當的支援,以同步讀取版面配置資訊,並在同一影格中更新 UI。這可讓您在元素顯示給使用者之前正確地定位它們。

如需更多詳細資訊,請參閱 useLayoutEffect

移除橋接器

在全新架構中,我們也完全移除了 React Native 對橋接器的依賴,改用 JSI 在 JavaScript 和原生程式碼之間進行直接、有效率的通訊

移除橋接器可透過避免橋接器初始化來縮短啟動時間。例如,在舊架構中,為了向 JavaScript 提供全域方法,我們需要在啟動時在 JavaScript 中初始化模組,導致應用程式啟動時間略有延遲

// ❌ Slow initialization
import {NativeTimingModule} from 'NativeTimingModule';
global.setTimeout = timer => {
NativeTimingModule.setTimeout(timer);
};

// App.js
setTimeout(() => {}, 100);

在全新架構中,我們可以從 C++ 直接繫結方法

// ✅ Initialize directly in C++
runtime.global().setProperty(runtime, "setTimeout", createTimer);
// App.js
setTimeout(() => {}, 100);

重寫也改善了錯誤報告,特別是針對啟動時的 JavaScript 當機,並減少了因未定義行為而導致的當機。如果發生當機,新的 React Native DevTools 可簡化偵錯並支援全新架構。

橋接器仍然存在以實現回溯相容性,以支援逐步遷移到全新架構。未來,我們將完全移除橋接器程式碼。

漸進式遷移

我們預期大多數應用程式都能以與其他任何版本相同的精力升級到 0.76。

當您升級到 0.76 時,全新架構和 React 18 預設為啟用。但是,若要使用並行功能並獲得全新架構的完整優勢,您的應用程式和函式庫將需要逐步遷移以完全支援全新架構。

當您首次升級時,您的應用程式將在全新架構上執行,並具有與舊架構的自動互通性層。對於大多數應用程式來說,這將在沒有任何變更的情況下運作,但互通性層存在 已知限制,因為它不支援存取自訂陰影節點或並行功能。

若要使用並行功能,應用程式也需要更新以支援 並行 React,方法是遵循 React 規則。若要將您的 JavaScript 程式碼遷移到 React 18 及其語意,請遵循 React 18 升級指南

整體策略是在不中斷現有程式碼的情況下,讓您的應用程式在全新架構上執行。然後,您可以按照自己的步調逐步遷移您的應用程式。對於已將所有模組遷移到全新架構的新介面,您可以立即開始使用並行功能。對於現有介面,您可能需要先解決一些問題並遷移模組,然後才能新增並行功能。

我們已與最熱門的 React Native 函式庫合作,以確保支援全新架構。超過 850 個函式庫已相容,包括所有每週下載次數超過 20 萬次的函式庫 (約佔已下載函式庫的 10%)。您可以在 reactnative.directory 網站上查看函式庫與全新架構的相容性

如需升級的更多詳細資訊,請參閱下方的 如何升級

新功能

全新架構包含對 React 18、並行功能和 React Native 中的 useLayoutEffect 的完整支援。如需 React 18 功能的完整清單,請參閱 React 18 部落格文章

Transitions

Transitions 是 React 18 中的新概念,用於區分緊急和非緊急更新。

  • 緊急更新反映直接互動,例如輸入和按下。
  • Transition 更新將 UI 從一個檢視轉換到另一個檢視。

緊急更新需要立即回應,以符合我們對實體物件行為方式的直覺。但是,轉換是不同的,因為使用者不希望看到螢幕上的每個中間值。在全新架構中,React Native 能夠支援分別呈現緊急更新和轉換更新。

通常,為了獲得最佳使用者體驗,單一使用者輸入應同時產生緊急更新和非緊急更新。與 ReactDOM 類似,presschange 等事件會被視為緊急事件並立即呈現。您可以在輸入事件內使用 startTransition API 來告知 React 哪些更新是「轉換」,可以延遲到背景

import {startTransition} from 'react';

// Urgent: Show the slider value
setCount(input);

// Mark any state updates inside as transitions
startTransition(() => {
// Transition: Show the results
setNumberOfTiles(input);
});

將緊急事件與轉換分開可實現更靈敏的使用者介面,以及更直覺的使用者體驗。

以下是在沒有轉換的舊架構和有轉換的新架構之間的比較。想像一下,每個圖磚都不是具有背景顏色的簡單檢視,而是一個包含影像和其他組件的豐富組件,這些組件的呈現成本很高。在使用 useTransition 之後,您可以避免因更新而導致應用程式效能下降並落後。

A video demonstrating an app rendering many views (tiles) according to a slider input. The views are rendered in batches as the slider is quickly adjusted from 0 to 1000.
之前:呈現圖磚,而不將其標記為轉換。
A video demonstrating an app rendering many views (tiles) according to a slider input. The views are rendered in batches as the slider is quickly adjusted from 0 to 1000. There are less batch renders in comparison to the next video.
之後:呈現具有轉換的圖磚,以中斷陳舊狀態的進行中呈現。

如需更多資訊,請參閱 對並行渲染器和功能的支援

自動批次處理

升級到全新架構後,您將受益於 React 18 的自動批次處理。

自動批次處理允許 React 在呈現時將更多狀態更新批次處理在一起,以避免呈現中間狀態。這讓 React Native 能夠更快且更不容易出現延遲,而無需開發人員的任何額外程式碼。

A video demonstrating an app rendering many views according to a slider input. The slider value is adjusted from 0 to 1000 and the UI slowly catches up to rendering 1000 views.
之前:使用舊版渲染器呈現頻繁的狀態更新。
A video demonstrating an app rendering many views according to a slider input. The slider value is adjusted from 0 to 1000 and the UI resolves to 1000 views faster than the previous example, without as many intermediate states.
之後:使用自動批次處理呈現頻繁的狀態更新。

在舊架構中,呈現了更多中間狀態,即使滑桿停止移動,UI 也會持續更新。新的架構呈現的中間狀態較少,並且由於自動批次處理更新,因此更快完成呈現。

如需更多資訊,請參閱 對並行渲染器和功能的支援

useLayoutEffect

在事件迴圈和同步讀取版面配置的能力的基礎上,在全新架構中,我們為 React Native 新增了對 useLayoutEffect 的適當支援。

在舊架構中,您需要使用非同步 onLayout 事件來讀取檢視的版面配置資訊 (這也是非同步的)。因此,至少有一個影格的版面配置不正確,直到讀取和更新版面配置,導致工具提示放置在錯誤位置等問題

// ❌ async onLayout after commit
const onLayout = React.useCallback(event => {
// ❌ async callback to read layout
ref.current?.measureInWindow((x, y, width, height) => {
setPosition({x, y, width, height});
});
}, []);

// ...
<ViewWithTooltip
onLayout={onLayout}
ref={ref}
position={position}
/>;

全新架構透過允許在 useLayoutEffect 中同步存取版面配置資訊來修正此問題

// ✅ sync layout effect during commit
useLayoutEffect(() => {
// ✅ sync call to read layout
const rect = ref.current?.getBoundingClientRect();
setPosition(rect);
}, []);

// ...
<ViewWithTooltip ref={ref} position={position} />;

此變更可讓您同步讀取版面配置資訊,並在同一影格中更新 UI,讓您在元素顯示給使用者之前正確地定位它們

A view that is moving to the corners of the viewport and center with a tooltip rendered either above or below it. The tooltip is rendered after a short delay after the view moves
在舊架構中,版面配置是在 onLayout 中非同步讀取的,導致工具提示的位置延遲。
A view that is moving to the corners of the viewport and center with a tooltip rendered either above or below it. The view and tooltip move in unison.
在全新架構中,可以在 useLayoutEffect 中同步讀取版面配置,在顯示之前更新工具提示位置。

如需更多資訊,請參閱 同步版面配置和效果的文件。

完整支援 Suspense

Suspense 可讓您宣告式地指定組件樹狀結構的一部分的載入狀態 (如果尚未準備好顯示)

<Suspense fallback={<Spinner />}>
<Comments />
</Suspense>

我們在幾年前推出了有限版本的 Suspense,而 React 18 新增了完整支援。到目前為止,React Native 無法支援 Suspense 的並行呈現。

全新架構包含對 React 18 中推出的 Suspense 的完整支援。這表示您現在可以在 React Native 中使用 Suspense 來處理組件的載入狀態,而暫停的內容將在背景中呈現,同時顯示載入狀態,從而提高可見內容上的使用者輸入的優先順序。

如需更多資訊,請參閱 React 18 中 Suspense 的 RFC

如何升級

若要升級至 0.76 版本,請依照發佈文章中的步驟進行。由於此版本也升級至 React 18,您還需要依照React 18 升級指南進行。

由於與舊架構的互通層,這些步驟應足以讓大多數應用程式升級至新架構。然而,若要充分利用新架構並開始使用並行功能,您需要遷移您的自訂原生模組和原生組件,以支援新的原生模組和原生組件 API。

若不遷移您的自訂原生模組,您將無法獲得共享 C++、同步方法調用或來自程式碼生成的型別安全的好處。若不遷移您的原生組件,您將無法使用並行功能。我們建議盡快將所有原生組件和原生模組遷移至新架構。

注意

在未來的版本中,我們將移除互通層,模組將需要支援新架構。

應用程式

如果您是應用程式開發人員,為了完全支援新架構,您需要升級您的函式庫、自訂原生組件和自訂原生模組,以完全支援新架構。

我們已與最受歡迎的 React Native 函式庫合作,以確保支援新架構。您可以在 reactnative.directory 網站上查看函式庫與新架構的相容性。

如果您的應用程式依賴的任何函式庫尚不相容,您可以

  • 向函式庫開啟 issue,並要求作者遷移至新架構。
  • 如果該函式庫沒有維護,請考慮具有相同功能的替代函式庫。
  • 選擇退出新架構,在這些函式庫遷移期間。

如果您的應用程式具有自訂原生模組或自訂原生組件,由於我們的 互通層,我們預期它們可以正常運作。但是,我們建議將它們升級到新的原生模組和原生組件 API,以完全支援新架構並採用並行功能。

請依照這些指南將您的模組和組件遷移至新架構

函式庫

如果您是函式庫維護者,請先測試您的函式庫是否與互通層一起運作。如果沒有,請在 新架構工作群組上開啟 issue。

為了完全支援新架構,我們建議盡快將您的函式庫遷移至新的原生模組和原生組件 API。這將允許您的函式庫使用者充分利用新架構並支援並行功能。

您可以依照這些指南將您的模組和組件遷移至新架構

選擇退出

如果由於任何原因,新架構在您的應用程式中無法正常運作,您可以隨時選擇退出,直到您準備好再次開啟它。

若要選擇退出新架構

  • 在 Android 上,修改 android/gradle.properties 檔案並關閉 newArchEnabled 標誌
-newArchEnabled=true
+newArchEnabled=false
  • 在 iOS 上,您可以透過執行以下命令重新安裝依賴項
RCT_NEW_ARCH_ENABLED=0 bundle exec pod install

感謝

將新架構交付給 OSS 社群是一項巨大的努力,我們花費了數年的研究和開發。我們想藉此機會感謝所有現任和前任 React 團隊成員,他們幫助我們實現了這個成果。

我們也對所有與我們合作實現這一目標的合作夥伴表示衷心感謝。特別是,我們要點名表揚

  • Expo,感謝他們早期採用新架構,並支持遷移最受歡迎的函式庫的工作。
  • Software Mansion,感謝他們維護生態系統中的關鍵函式庫,早期將它們遷移到新架構,以及在調查和修復各種問題方面提供的所有幫助。
  • Callstack,感謝他們維護生態系統中的關鍵函式庫,早期將它們遷移到新架構,以及對 Community CLI 工作提供的支持。
  • Microsoft,感謝他們為 react-native-windowsreact-native-macos 以及其他幾個開發人員工具新增了新架構的實作。
  • ExpensifyKrakenBlueSkyBrigad,感謝他們率先採用新架構並報告各種問題,以便我們可以為所有人修復這些問題。
  • 所有獨立的函式庫維護者和開發人員,他們透過測試新架構、修復一些問題以及針對不明確的事項提出問題,為新架構做出了貢獻,以便我們可以釐清這些問題。