React Labs:我們最近的工作進展 - 2023 年 3 月

2023 年 3 月 22 日,由 Joseph SavonaJosh StoryLauren Tan陳夢迪 (Mengdi Chen)Samuel SuslaSathya GunasekaranSebastian MarkbågeAndrew Clark 發表


在 React Labs 的文章中,我們會撰寫關於積極研發中項目的內容。自從我們上次更新以來,我們在這些項目上取得了重大進展,我們想分享我們學到的東西。


React 伺服器元件

React 伺服器元件(或 RSC)是由 React 團隊設計的一種新的應用程式架構。

我們首先在介紹性演講RFC中分享了我們對 RSC 的研究。總之,我們正在引入一種新的元件類型 - 伺服器元件 - 它會提前運行,並且會從你的 JavaScript 捆綁包中排除。伺服器元件可以在構建期間運行,讓你從檔案系統讀取或擷取靜態內容。它們也可以在伺服器上運行,讓你無需構建 API 即可存取你的資料層。你可以透過 props 將資料從伺服器元件傳遞到瀏覽器中的互動式客戶端元件。

RSC 結合了以伺服器為中心的「請求/回應」多頁應用程式心智模型,以及以客戶端為中心的單頁應用程式的無縫互動性,讓你兩全其美。

自從我們上次更新以來,我們已合併React 伺服器元件 RFC以批准該提案。我們解決了React 伺服器模組約定提案中懸而未決的問題,並與我們的合作夥伴達成共識,採用"use client"約定。這些文件也作為 RSC 相容實作應支援內容的規範。

最大的變化是我們引入了async / await 作為從伺服器元件進行資料擷取的主要方式。我們還計畫透過引入一個名為 use 的新 Hook 來支援從客戶端載入資料,該 Hook 可以解開 Promises。雖然我們無法在純客戶端應用程式的任意元件中支援 async / await,但我們計畫在你將純客戶端應用程式的結構與 RSC 應用程式的結構類似時新增對它的支援。

現在我們已經很好地解決了資料擷取問題,我們正在探索另一個方向:將資料從客戶端發送到伺服器,以便你可以執行資料庫異動和實作表單。我們正在透過允許你跨伺服器/客戶端邊界傳遞伺服器動作函數來做到這一點,客戶端可以呼叫這些函數,提供無縫的 RPC。伺服器動作還可以在 JavaScript 載入之前提供漸進增強的表單。

React 伺服器元件已在Next.js App Router中發布。這展示了一個真正將 RSC 作為原語的路由器的深度整合,但它並不是構建 RSC 相容路由器和框架的唯一方法。RSC 規範和實作提供的功能有明確的區分。React 伺服器元件旨在作為適用於相容 React 框架的元件規範。

我們通常建議使用現有的框架,但如果你需要構建自己的自定義框架,這是可能的。構建你自己的 RSC 相容框架並不像我們希望的那麼容易,主要原因是需要深度整合捆綁器。當前這一代的捆綁器非常適合在客戶端上使用,但它們的設計並沒有考慮到對在伺服器和客戶端之間分割單個模組圖的支援。這就是為什麼我們現在直接與捆綁器開發人員合作,將 RSC 的原語內建到捆綁器中的原因。

資源載入

Suspense 允許你指定在元件的資料或程式碼仍在載入時在螢幕上顯示的內容。這讓你的使用者可以在頁面載入期間以及在載入更多資料和程式碼的路由器導覽期間逐步看到更多內容。但是,從使用者的角度來看,在考慮新內容是否準備就緒時,資料載入和渲染並不能說明全部情況。預設情況下,瀏覽器會獨立載入樣式表、字型和圖片,這可能會導致 UI 跳轉和連續的版面配置偏移。

我們正努力將 Suspense 與樣式表、字型和圖片的載入生命週期完全整合,以便 React 將它們考慮在內,以確定內容是否已準備好顯示。無需更改你編寫 React 元件的方式,更新將以更一致且令人愉快的方式進行。作為一種優化,我們還將提供一種手動方式來直接從元件預載資源,例如字型。

我們目前正在實作這些功能,很快就會有更多資訊可以分享。

文件詮釋資料

你的應用程式中的不同頁面和螢幕可能具有不同的詮釋資料,例如 `<title>` 標籤、描述和其他特定於此螢幕的 `<meta>` 標籤。從維護的角度來看,將這些資訊保留在該頁面或螢幕的 React 元件附近更具擴展性。然而,這些詮釋資料的 HTML 標籤需要放在文件 `<head>` 中,而 `<head>` 通常會在應用程式最根部的元件中渲染。

目前,人們使用以下兩種技術之一來解決這個問題。

一種技術是渲染一個特殊的第三方元件,將 `<title>`、`<meta>` 和其他標籤移動到文件 `<head>` 中。這適用於大多數瀏覽器,但許多客戶端(例如 Open Graph 解析器)並未運行客戶端 JavaScript,因此這種技術並非普遍適用。

另一種技術是將頁面分為兩部分進行伺服器端渲染。首先,渲染主要內容並收集所有此類標籤。然後,使用這些標籤渲染 `<head>`。最後,將 `<head>` 和主要內容發送到瀏覽器。這種方法有效,但它會阻止你利用 React 18 的串流伺服器渲染器,因為你必須等待所有內容渲染完成後才能發送 `<head>`。

這就是為什麼我們要新增內建支援,以便在你的元件樹中的任何位置直接渲染 `<title>`、`<meta>` 和詮釋資料 `<link>` 標籤。它在所有環境中都以相同的方式運作,包括完全客戶端程式碼、SSR,以及未來的 RSC。我們很快就會分享更多相關細節。

React 優化編譯器

自上次更新以來,我們一直在積極迭代 React Forget(React 的優化編譯器)的設計。我們之前將其稱為「自動記憶體編譯器」,在某種意義上確實如此。但構建編譯器幫助我們更深入地理解了 React 的程式設計模型。理解 React Forget 的更好方法是將其視為自動*響應式*編譯器。

React 的核心思想是開發人員將 UI 定義為當前狀態的函式。你使用純 JavaScript 值(數字、字串、陣列、物件)和標準 JavaScript 語法(if/else、for 等)來描述你的元件邏輯。心智模型是 React 會在應用程式狀態發生變化時重新渲染。我們相信這種簡單的心智模型和貼近 JavaScript 語義是 React 程式設計模型中的重要原則。

問題是 React 有時可能*過於*響應式:它可能會重新渲染過多。例如,在 JavaScript 中,我們沒有廉價的方法來比較兩個物件或陣列是否相等(具有相同的鍵和值),因此在每次渲染時創建新的物件或陣列可能會導致 React 執行比其嚴格需要的更多工作。這表示開發人員必須明確地記憶體元件,以免對更改過度反應。

我們使用 React Forget 的目標是確保 React 應用程式預設情況下具有恰到好處的響應式:應用程式僅在狀態值*有意義地*更改時才重新渲染。從實作的角度來看,這表示自動記憶體,但我們相信響應式框架是理解 React 和 Forget 的更好方法。理解這一點的一種方法是,React 目前在物件身分更改時重新渲染。使用 Forget,React 在語義值更改時重新渲染,但不會產生深度比較的運行時成本。

就具體進展而言,自上次更新以來,我們已根據這種自動響應式方法大幅迭代了編譯器的設計,並納入了內部使用編譯器的回饋。從去年年底開始對編譯器進行一些重大重構後,我們現在已開始在 Meta 的有限區域內在生產環境中使用編譯器。我們計劃在生產環境中驗證後將其開源。

最後,很多人對編譯器的工作原理表示感興趣。我們期待在驗證編譯器並將其開源後分享更多細節。但現在我們可以分享一些資訊

編譯器的核心幾乎與 Babel 完全解耦,核心編譯器 API(大致)是舊 AST 輸入,新 AST 輸出(同時保留原始碼位置數據)。在底層,我們使用自定義程式碼表示和轉換管道來執行低階語義分析。但是,編譯器的主要公共介面將通過 Babel 和其他建置系統插件。為了便於測試,我們目前有一個 Babel 插件,它是一個非常薄的包裝器,它呼叫編譯器來產生每個函式的 新版本並將其交換進去。

在過去幾個月中重構編譯器時,我們希望專注於改進核心編譯模型,以確保我們能夠處理條件、迴圈、重新賦值和突變等複雜性。但是,JavaScript 有許多方法可以表達每個特性:if/else、三元運算子、for、for-in、for-of 等。嘗試預先支援完整的語言會延遲我們驗證核心模型的時間點。相反,我們從一個小型但具有代表性的語言子集開始:let/const、if/else、for 迴圈、物件、陣列、基元、函式呼叫和其他一些特性。隨著我們對核心模型的信心增強並改進我們的內部抽象,我們擴展了支援的語言子集。我們也明確說明了我們尚不支援的語法,記錄診斷資訊並跳過不支援輸入的編譯。我們有一些工具可以在 Meta 的程式碼庫上試用編譯器,並查看哪些不支援的特性最常見,以便我們可以優先考慮下一個要支援的特性。我們將繼續逐步擴展以支援整個語言。

使 React 元件中的純 JavaScript 具有響應式需要一個對語義有深入理解的編譯器,以便它能夠準確地理解程式碼正在做什麼。通過採用這種方法,我們正在 JavaScript 中創建一個響應式系統,讓你能夠使用語言的完整表達能力編寫任何複雜度的產品程式碼,而不受限於特定領域的語言。

螢幕外渲染

螢幕外渲染是 React 中即將推出的功能,可在背景中渲染螢幕而不會產生額外的效能負擔。你可以將其視為 content-visibility CSS 屬性 的一個版本,它不僅適用於 DOM 元素,也適用於 React 元件。在我們的研究中,我們發現了各種用例

  • 路由器可以在背景中預渲染螢幕,以便在使用者瀏覽到它們時立即顯示。
  • 標籤切換元件可以保留隱藏標籤的狀態,以便使用者在它們之間切換而不會遺失進度。
  • 虛擬化列表元件可以在可視視窗上方和下方預渲染額外的行。
  • 開啟模態視窗或彈出視窗時,應用程式的其餘部分可以進入「背景」模式,以便除模態視窗外,所有內容的事件和更新都被禁用。

大多數 React 開發人員不會直接與 React 的螢幕外 API 互動。相反,螢幕外渲染將被整合到路由器和 UI 函式庫之類的東西中,然後使用這些函式庫的開發人員將自動受益,而無需額外的工作。

想法是,你應該能夠在螢幕外渲染任何 React 樹,而無需更改編寫元件的方式。當元件在螢幕外渲染時,它實際上並不會*掛載*,直到元件變得可見為止——它的效果不會被觸發。例如,如果元件使用 `useEffect` 在第一次出現時記錄分析,預渲染不會弄亂這些分析的準確性。同樣,當元件移出螢幕時,它的效果也會被卸載。螢幕外渲染的一個關鍵特性是你可以切換元件的可見性而不會遺失其狀態。

自上次更新以來,我們已在 Meta 內部針對 Android 和 iOS 上的 React Native 應用程式測試了預渲染的實驗版本,並取得了正面的效能成果。我們還改進了螢幕外渲染與 Suspense 的配合方式——在螢幕外樹中暫停不會觸發 Suspense 後備。我們剩餘的工作包括最終確定公開給函式庫開發人員的基元。我們預計在今年晚些時候發布 RFC,以及用於測試和回饋的實驗性 API。

轉換追蹤

轉換追蹤 API 讓您能夠偵測到 React 轉換 何時變慢,並調查變慢的原因。繼上次更新後,我們已完成 API 的初始設計,並發布了 RFC。基本功能也已實作。該專案目前暫停中。我們歡迎您針對 RFC 提供意見回饋,並期待恢復其開發,以便為 React 提供更好的效能測量工具。這對於構建在 React Transitions 之上的路由器(例如 Next.js App Router)尤其有用。


除了這次更新之外,我們的團隊最近也參與了社群 podcast 和直播,更深入地講解我們的工作並回答問題。

感謝 Andrew ClarkDan AbramovDave McCabeLuna Wei (魏露娜)Matt CarrollSean KeeganSebastian SilbermannSeth WebsterSophie Alpert 審閱這篇文章。

感謝您的閱讀,下次更新再見!