重新思考 React Server Component

date
Jun 3, 2024
slug
react-server-component
status
Published
tags
Frontend
React
summary
React Server Component 十分複雜,名字也很容易被誤解,此篇就來談談為何會有他的出現
type
Post
在和許多人討論到 React Server Component 時,很多人都有個疑問就是為什麼我們需要 React Server Component?亦或是這和原本的 Server Side Rendering 有什麼差別,此篇文章想簡單談一下這幾題,希望能幫助大家釐清這幾個問題。

先從 React 談起

React 是一個純瀏覽器用的 library,或更精確的說是因為使用 react-dom,早期最簡單的 Single-page Application (SPA) 便是使用 script tag 載入打包好的前端代碼,react-dom 會指定存在的 DOM element 進行掛載,initial rendering 完後便有了初始的畫面 (First Paint)。
但這麼做的話,從 js 下載、解析、執行完成前都會是白畫面,使用者體驗不好,因此才有在伺服器端產生 intial HTML 的做法出現。

改善 First Paint 的救星:Server Side Rendering (SSR)

這是一個很直覺的作法,在伺服器端先提早產完 initial HTML,除了使用者一開始便能看到畫面外,HTML document 也是能被瀏覽器或是 CDN 緩存的。
💡
無論是 build time 提早產完,或是 runtime on-demand 地做 initial rendering,都可以視為 Server Side Rendering。
但這些預產好的 DOM Element 和 React 之間是脫離的,React 並無法知道哪個 DOM Element 對應到哪個 Component,因此我們需要做一次「Hydration」。Hydration 所做的事情就是 React 仍跑一次 rendering,並把 React Tree 和 DOM Tree 之間做比對,依序找出 Component 和 DOM Element 之間的對應關係,由此便能正確處理 interactivity (ex. event handler)。
💡
React 的本質在於宣告式的寫法,能夠重新渲染也是依賴在狀態和狀態間的 Diff,因此初始的狀態是重要的,這也是為什麼如果初始狀態有問題 (ex. SSR 和 CSR 結果不一致) 就會噴個 Hydration Mismatch Error,避免一步錯步步錯的問題產生。
到這邊一個 Server Side Rendering 的 App 就大功告成了,但大多數的框架仍以此出發,想說已經在伺服器端做渲染了,那不如先拿好資料好了,因此就有了 Next.js 的 getOOOOProps、Remix 的 loader 出現。
雖說能大幅改善傳統 effect hook 拿取資料造成 client-server 來回的窘境,但大部分的框架在此都只能在 page level 去拉取資料再注入到 Component 上,除了增加維護上的難度外,大量的 API 呼叫也會造成 SSR 變慢,要實作資料 Lazy Load 也只能再拖回瀏覽器端,這也透露著此時的 React 生態系在處理 Data Fetching 上的不足。
除了 Data Fetching 上待尋更好的解法外,日漸肥大的 js 檔案以及 Hydration 也是效能上的一大痛點,因此 React Team 便由此下手,提出了新的解法「React Server Component」。
💡
在 React Server Component 出現之前,React 其實也有提出對應的解決方案「Selective Hydration」以及「Suspense for Data Fetching」。 前者是 React 18 中透過 hydrateRoot() 便可配合 Suspense 讓提早回來的元素做 hydration。而後者也是藉由 Suspense 來讓 Rendering 和 Data Fetching 間有關聯,不過這個當時還在實驗階段。

嶄新的架構:React Server Component

2020 年 React Team 便提出了 React Server Component (RSC) 的概念,將 Component 移到伺服器端做渲染,而且只在伺服器端做,因此能減少 js script 的大小,也能省去 Hydration 的執行。
💡
Server Component 並不是完全不會帶回 js script,如前面所提,React 仍須知道初始渲染的結果,因此 server component 會帶回 render 後的結果,只是是用 wire format 的形式表現,所以大小上相較 Client Component 小。
而此架構下因為是發生在伺服器端因此也能在 Component 上直接拉取資料,並能利用 Suspense 達到的 Lazy Load 的效果。雖說新架構的出現著實解決了原先的痛點,但也相對更依賴在伺服器端了。
除此之外,RSC 的引入仍有一些待解的問題 (ex. 資料在 component 拿的話要如何決定 page status code、server component 無法 set header、server action 可能有 version skew 產生…),但隨著各個框架們開始導入後,應該也能期待解法以及新的 Best Practice 出現。而前陣子發布的 React 19 也釋出了許多新東西,未來很值得期待!
 
最後附上一些很棒的參考資料:
 

© maxam 2023 - 2024