React Server Component: 混合式渲染

来源:https://www.cnblogs.com/ClientInfra/archive/2022/11/29/16935937.html
-Advertisement-
Play Games

相信大家對 React Server Component 有所耳聞,React 團隊對它是這樣介紹的: zero-bundle-size React Server Components。這是一種實驗性探索,但相信該探索是個未來 React 發展的方向,與 React Server Component... ...


作者:謝奇璇

React 官方對 Server Comopnent 是這樣介紹的: zero-bundle-size React Server Components。

這是一種實驗性探索,但相信該探索是個未來 React 發展的方向,與 React Server Component 相關的周邊生態正在積極的建設當中。

術語介紹

在 React Server Component (以下稱 Server Component) 推出之後,我們可以簡單的將 React 組件區分為以下三種:

Server Component 服務端渲染組件,擁有訪問資料庫、訪問本地文件等能力。無法綁定事件對象,即不擁有交互性。
Client Component 客戶端渲染組件,擁有交互性。
Share Component 既可以在服務端渲染,又可以在客戶端渲染。具體如何渲染取決於誰引入了它。當被服務端組件引入的時候會由服務端渲染當被客戶端組件引入的時候會由客戶端渲染。

React 官方暫定通過「文件名尾碼」來區分這三種組件:

  1. Server Component 需要以 .server.js(/ts/jsx/tsx) 為尾碼
  2. Client Component 需要以 .client.js(/ts/jsx/tsx) 為尾碼
  3. Share Component 以 .js(/ts/jsx/tsx) 為尾碼

混合渲染

簡單來說 Server Component 是在服務端渲染的組件,而 Client Component 是在客戶端渲染的組件。

與類似 SSR , React 在服務端將 Server Component 渲染好後傳輸給客戶端,客戶端接受到 HTML 和 JS Bundle 後進行組件的事件綁定。不同的是:Server Component 只進行服務端渲染,不會進行瀏覽器端的 hyration(註水),總的來說頁面由 Client Component 和 Server Component 混合渲染。

這種渲染思路有點像 Islands 架構,但又有點不太一樣。

如圖:橙色為 Server Component, 藍色為 Client Component 。

React 是進行混合渲染的?

React 官方提供了一個簡單的 Demo , 通過 Demo,探索一下React sever component的運作原理。

渲染入口

瀏覽器請求到 HTML 後,請求入口文件 - main.js, 裡面包含了 React Runtime 與 Client Root,Client Root 執行創建一個 Context,用來保存客戶端狀態,與此同時,客戶端向服務端發出 /react 請求。

// Root.client.jsx 偽代碼

function Root() {
    const [data, setData] = useState({});
    
    // 向服務端發送請求
    const componentResponse = useServerResponse(data);
    return (
        <DataContext.Provider value={[data, setData]}> 
            componentResponse.render();
        </DataContext.Provider>
    );
}

看出這裡沒有渲染任何真實的 DOM, 真正的渲染會等 response 返回 Component 後才開始。

請求服務端組件

Client Root 代碼執行後,瀏覽器會向服務端發送一個帶有 data 數據的請求,服務端接收到請求,則進行服務端渲染。

服務端將從 Server Component Root 開始渲染,一顆混合組件樹將在服務端渲染成一個巨大的 VNode。

如圖,這一顆混合組件樹會被渲染成這樣一個對象,它帶有 React 組件所有必要的信息。

module.exports = {
    tag: 'Server Root',
    props: {...},
    children: [
        { tag: "Client Component1", props: {...}: children: [] },
        { tag: "Server Component1", props: {...}: children: [
            { tag: "Server Component2", props: {...}: children: [] },
            { tag: "Server Component3", props: {...}: children: [] },
        ]}
    ]
}

不僅僅是這樣一個對象, 由於 Client Comopnent 需要 Hydration, React 會將這部分必須要的信息也返回回去。React 最終會返回一個可解析的 Json 序列 Map。

M1:{"id":"./src/BlogMenu.client.js","chunks":["client0"],"name":"xxx"}
J0:["$","main", null, ["]]
  • M:  代表 Client Comopnent 所需的 Chunk 信息
  • J:  代表 Server Compnent 渲染出的類 react element格式的字元串

React Runtime 渲染

組件數據返回給瀏覽器後,React Runtime 開始工作,將返回的 VNode 渲染出真正的 HTML。與此同時,發出請求,請求 Client Component 所需的 JS Bundle。當瀏覽器請求到 Js Bundle 後,React 就可以進行選擇性 Hydration(Selective Hydration)。需要註意的是, React 團隊傳輸組件數據選擇了流式傳輸,這也意味著 React Runtime 無需等待所有數據獲取完後才開始處理數據。

啟動流程

  1. 瀏覽器載入 React Runtime, Client Root 等 js 代碼
  2. 執行 Client Root 代碼,向服務端發出請求
  3. 服務端接收到請求,開始渲染組件樹
  4. 服務端將渲染好的組件樹以字元串的信息返回給瀏覽器
  5. React Runtime 開始渲染組件且向服務端請求 Client Component Js Bundle 進行選擇性 Hydration(註水)

Client <-> Server 如何通信?

Client Component 與 Server Component 有著天然的環境隔離,他們是如何互相通信的呢?

Server Component -> Client Component

在服務端的渲染都是從 Server Root Component 開始的,Server Component 可以簡單的通過 props 向 Client Component 傳遞數據。

import ClientComponent from "./ClientComponent";

const ServerRootComponent = () => {
    return <ClientComponent title="xxx" />
};

但需要註意的是:這裡傳遞的數據必須是可序列化的,也就是說你無法通過傳遞 Function 等數據。

Client Component  -> Server Component

Client Component 組件通過 HTTP  向服務端組件傳輸信息。Server Component 通過 props 的信息接收數據,當 Server Component 拿到新的 props 時會進行重新渲染, 之後通過網路的手段發送給瀏覽器,通過 React Runtime 渲染在瀏覽器渲染出最新的 Server Component UI。這也是 Server Component 非常明顯的劣勢:渲染流程極度依賴網路。

// Client Component
function ClientComponent() {
    const sendRequest = (props) => {
        const payload = JSON.stringify(props);
        fetch(`http://xxxx:8080/react?payload=${payload}`)
    }
    return (
        <button 
           onclick = {() => sendRequest({ messgae: "something" })}
        >
            Click me, send some to server
        </button>
    )
}
// Serve Component
const ServerRootComponent = ({ messgae: "something" }) => {
    return <ClientComponent title="xxx" />
};

Server Component 所帶來的優勢

RSC 推出的背景是 React 官方想要更好的用戶體驗,更低的維護成本,更高的性能。通常情況下這三者不能同時獲得,但 React 團隊覺得「小孩子才做選擇,我全都要」。

根據官方提出 RFC: React Server Components,可以通過以下幾點能夠看出 React 團隊是如何做到"全都要"的:

更小的 Bundle 體積

通常情況下,我們在前端開發上使用很多依賴包,但實際上這些依賴包的引入會增大代碼體積,增加 bundle 載入時間,降低用戶首屏載入的體驗。

例如在頁面上渲染 MarkDown ,我們不得不引入相應的渲染庫,以下麵的 demo 為例,不知不覺我們引入了  240 kb 的 js 代碼,而且往往這種大型第三方類庫是沒辦法進行 tree-shaking。

// NOTE: *before* Server Components

import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)

function NoteWithMarkdown({text}) {
  const html = sanitizeHtml(marked(text));
  return (/* render */);
}

可以想象,為了某一個計算任務,我們需要將大型 js 第三方庫傳輸到用戶瀏覽器上,瀏覽器再進行解析執行它來創造計算任務的 runtime, 最後才是計算。從用戶的角度來講:「我還沒見到網頁內容,你就占用了我較大帶寬和 CPU 資源,是何居心」。然而這一切都是可以省去的,我們可以利用 SSR 讓 React 在服務端先渲染,再將渲染後的 html 發送給用戶。從這一方面看,Server Component 和 SSR 很類似,但不同的是 SSR 只能適用於首頁渲染,Server Component 在用戶交互的過程中也是服務端渲染,Server Component 傳輸的也不是 html 文本,而是 json。Server Component 在服務端渲染好之後會將一段類 React 組件 json 數據發送給瀏覽器,瀏覽器中的 React Runtime 接收到這段 json  數據 後,將它渲染成 HTML。

我們舉一個更加極端的例子:若用戶無交互性組件,所以組件都可以在服務端渲染,那麼所有 UI 渲染都將走「瀏覽器接收"類 react element 文本格式"的數據,React Runtime 渲染」的形式進行渲染。 那麼除了一些 Runtime, 我們無需其他 JS Bundle。而 Runtime 的體積是不會隨著項目的增大而增大的,這種常數繫數級體積也可以稱為 "Zero-Bundle-Size"。因此官方這稱為: "Zero-Bundle-Size Components"。

更好的使用服務端能力

為了獲取數據,前端通常需要請求後端介面,這是因為瀏覽器是沒辦法直接訪問資料庫的。但既然我們都藉助服務端的能力了,那我們當然可以直接訪問資料庫,React 在伺服器上將數據渲染進組件。

通過自由整合後端能力,我們可以解決:「網路往返過多」和「數據冗餘」問題。甚至我們可以根據業務場景自由地決定數據存儲位置,是存儲在記憶體中、還是存儲在文件中或者存儲在資料庫。除了數據獲取,還可以再開一些"腦洞"。

  • 我們可以在 Server Component 的渲染過程中將一些高性能計算任務交付給其他語言,如 C++,Rust。這不是必須的,但你可以這麼做。
  • ......

簡單粗暴一點的說:Nodejs 擁有什麼樣的能力,你的組件就能擁有什麼能力。

更好的自動化 Code Split

在過去,我們可以通過 React 提供的 lazy + Suspense 進行代碼分割。這種方案在某些場景(如 SSR)下無法使用,社區比較成熟的方案是使用第三方類庫 @loadable 。然而無論是使用哪一種,都會有以下兩個問題:

  1. Code Split 需要用戶進行手動分割,自行確認分割點。
  2. 與其說是 Code Split,其實更偏向懶載入。也就是說,只有載入到了代碼切割點,我們才會去即時載入所切割好的代碼。這裡還是存在一個載入等待的問題,削減了code split給性能所帶來的好處。

React核心團隊所提出 Server Component 可以幫助我們解決上面的兩個問題。

  1. React Server Component 將所有 Client Component 的導入視為潛在的分割點。也就是說,你只需要正常的按分模塊思維去組織你的代碼。React 會自動幫你分割
import ClientComponent1 from './ClientComponent1';


function ServerComponent() {
    return (
        <div>
            <ClientComponent1 />
        </div>
    )
}
  1. 框架側可以介入 Server Component 的渲染結果,因此上層框架可以根據當前請求的上下文來預測用戶的下一個動作,從而去「預載入」對應的js代碼。

避免高度抽象所帶來的性能負擔

React server component通過在伺服器上的實時編譯和渲染,將抽象層在伺服器進行剝離,從而降低了抽象層在客戶端運行時所帶來的性能開銷。

舉個例子,如果一個組件為了可配置行,被多個 wrapper 包了很多層。但事實上,這些代碼最終只是渲染為一個<div>。如果把這個組件改造為 server component 的話,那麼我們只需要往客戶端返回一個<div>字元串即可。下麵例子,我們通過把這個組件改造為server component,那麼,我們大大降低網路傳輸的資源大小和客戶端運行時的性能開銷:

// Note.server.js
// ...imports...

function Note({id}) {
  const note = db.notes.get(id);
  return <NoteWithMarkdown note={note} />;
}

// NoteWithMarkdown.server.js
// ...imports...

function NoteWithMarkdown({note}) {
  const html = sanitizeHtml(marked(note.text));
  return <div ... />;
}

// client sees:
<div>
  <!-- markdown output here -->
</div>

參考自:
https://juejin.cn/post/6918602124804915208#heading-5

我們可以通過在 Server Component ,將 HOC 組件進行渲染,可能渲染到最後只是一個 <div> 我們就無需將 bundle 傳輸過去,也無需讓瀏覽器消耗性能去渲染。

Sever Component 可能存在的劣勢

弱網情況下的交互體驗

如上文所述: React Server Component 的邏輯, 他的渲染流程依靠網路。服務端渲染完畢後將類 React 組件字元串的數據傳輸給瀏覽器,瀏覽器中的 Runtime React 再進行渲染。顯然,在弱網環境下,數據傳輸會很慢,渲染也會因為網速而推遲,極大的降低了用戶的體驗。Server Component 比較難能可貴的是,它跟其他技術並不是互斥的,而是可以結合到一塊。例如:我們完全可以將 Server Component 的計算渲染放在邊緣設備上進行計算,在一定程度上能給降低網路延遲帶來的問題。

開發者的心智負擔

在 React Server Component 推出之後,開發者在開發的過程中需要去思考: 「我這個組件是 Server Component 還是 Client Component」,在這一方面會給開發者增加額外的心智負擔,筆者在寫 Demo 時深有體會,思維上總是有點不習慣。Nextjs 前一段時間發佈了 v13,目前已實現了 Server & Client Component 。參考 Next13 的方案,預設情況下開發者開發的組件都是 Server Component ,當你判斷這個組件需要交互或者調用 DOM, BOM 相關 API 時,則標記組件為 Client Component。

「預設走 Server Component,若有交互需要則走 Client Component」 通過這種原則,相信在一定程度上能給減輕開發者的心智負擔。

應用場景: 文檔站

從上面我們可以知道 Server Component 在輕交互性的場景下能夠發揮它的優勢來,輕交互的場景一般我們能想到文檔站。來看一個小 Demo, 通過這個 Demo 我們觀察到幾個現象:

  1. 極小的 Js bundle。
  2. 文件修改無需 Bundle。

當然像文檔站等偏向靜態的頁面更適合 SSR, SSG,但就像前面所說的它並不與其他的技術互斥,我們可以將其進行結合,更況且他不僅僅能應用於這樣的靜態場景。

參考文檔


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 溫馨提示,請使用ctrl+F進行快速查找 ws2_32.lib error LNK2001: 無法解析的外部符號 __imp_htons error LNK2001: 無法解析的外部符號 __imp_ntohl error LNK2001: 無法解析的外部符號 __imp_ntohs error L ...
  • 1、什麼是MQTT? MQTT(Message Queuing Telemetry Transport,消息隊列遙測傳輸協議),是一種基於發佈/訂閱(publish/subscribe)模式的"輕量級"通訊協議,該協議構建於TCP/IP協議上,由IBM在1999年發佈。MQTT最大優點在於,可以以極 ...
  • 1.玻利維亞 MOPSV 為 5G 移動服務分配 3.3-3.6 GHz 頻段 https://www.oopp.gob.bo/wp-content/uploads/2022/10/2022-RM-174-Modificacion-al-Plan-Nacional-de-Frecuencia.pdf ...
  • 視圖 create view ... as ps:SQL文件在上一篇博客末尾 視圖就是通過查詢得到一張虛擬表,然後保存下來,下次直接使用 create view teacher_course as select * from teacher inner join course on teacher. ...
  • 10.1 事務的基本概念: 什麼是事務?事務是用戶定義的一個資料庫操作序列,該操作要麼全做,要麼全不做,是一個不可分割的工作單位,是恢復(知識點)和併發控制(知識點)的基本單位 事務和程式的區別: 在關係資料庫中,一個事務可以是一條SQL語句,或多條SQL語句,或整個程式 一個程式可以有多個事務 事 ...
  • 本篇開啟資料庫在工作中常用到的格式轉換與工具,歡迎大家評論留言:smile: SQL將小數轉為保留兩位的百分數 CONCAT(CONVERT((<需要轉換的值>)*100,DECIMAL(18,2)),'%') turnNum 常用的日期格式化 引用的是CSDN博主isTrueLoveColour的 ...
  • 案例介紹 歡迎來到我的小院,我是霍大俠,恭喜你今天又要進步一點點了!我們來用JavaScript相關知識,做一個隨機點名的案例。你可以通過點擊開始按鈕控制上方名字的閃動,點擊停止按鈕可以隨機選定一個名字。 案例演示 運行程式後,我們可以看到一個矩形框按鈕,顯示開始點名,點擊後名字隨機閃動。同時按鈕變 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 前言 對於前端人員來講,最令人頭疼的應該就是頁面性能了,當用戶在訪問一個頁面時,總是希望它能夠快速呈現在眼前並且是可交互狀態。如果頁面載入過慢,你的用戶很可能會因此離你而去。所以頁面性能對於前端開發者來說可謂是重中之重,其實你如果瞭解頁面 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...