React Server Component 可能并沒有那么香

前段時(shí)間 React 團(tuán)隊(duì)發(fā)布了一項(xiàng)用于解決 React 頁面在多接口請(qǐng)求下的性能問題的解決方案 React Server Components[1]。當(dāng)然該方案目前還在草案階段,官方也只是發(fā)了視頻和一個(gè)示例 demo[2] 來說明這個(gè)草案[3]。
Server Components
官方在視頻和 RFC 中說明了產(chǎn)生這個(gè)方案的主要原因是因?yàn)榇罅康?React 組件依賴數(shù)據(jù)請(qǐng)求才能做渲染。如果每個(gè)組件自己去請(qǐng)求數(shù)據(jù)的話會(huì)出現(xiàn)子組件要等父組件數(shù)據(jù)請(qǐng)求完成渲染子組件的時(shí)候才會(huì)開始去請(qǐng)求子組件的數(shù)據(jù),也就是官方所謂的 WaterFall 數(shù)據(jù)請(qǐng)求隊(duì)列的問題。而將數(shù)據(jù)請(qǐng)求放在一起請(qǐng)求又非常不便于維護(hù)。

既然組件需要數(shù)據(jù)才能渲染,那為什么接口不直接返回渲染后的組件呢?所以他們提出了 Server Components 的解決方案。我們暫且不管這其中的邏輯有沒有道理,先來看看該方案的大體流程是怎樣的。
方案的大概就是將 React 組件拆分成 Server 組件(.server.tsx)和 Client 組件(.client.tsx)兩種類型。其中 Server 組件會(huì)在服務(wù)端直接渲染并返回。與 SSR 的區(qū)別是 Server Components 返回的是序列化的組件數(shù)據(jù),而不是最終的 HTML。

可能帶來的問題
通過接口將組件和組件的數(shù)據(jù)一并返回的方式帶來了打包體積的優(yōu)勢(shì),但是它真的能像 React Hooks 一樣香嗎?我覺得并不然。
接口返回
常規(guī)做法里前端 JS 中加載組件,接口返回組件需要的數(shù)據(jù)。而 React Server Components 中則是將二者合二為一,雖然在打包體積上有所優(yōu)化,但是明顯是把這體積轉(zhuǎn)義到了接口返回中。特別是在類似列表這種有分頁的請(qǐng)求中,這種劣勢(shì)會(huì)更明顯。明明組件只需要在初始的時(shí)候進(jìn)行加載,但是因?yàn)楸蝗诤线M(jìn)接口里了,每次接口都會(huì)返回冗余的組件結(jié)構(gòu),這樣也不知道是好還是不好??赡芎罄m(xù)需要優(yōu)化一下接口二次返回只返回?cái)?shù)據(jù)會(huì)比較好。
服務(wù)器成本問題
這里所說的服務(wù)器成本有很多,首先是機(jī)器本身的成本。將客戶端渲染行為遷移到服務(wù)端時(shí)候勢(shì)必會(huì)增加服務(wù)端的壓力,用戶量上來之后這塊的成本是成量級(jí)的在增加的。關(guān)于這個(gè)問題,官方提供的回復(fù)是隨著服務(wù)器的成本降低勢(shì)必 Server Components 帶來的優(yōu)勢(shì)會(huì)抵消這塊的劣勢(shì)。
Question: This might become more expensive for applications. In the search demo, finding those search results plus rendering them on the server is a more expensive operation than just an API call sent from the client.
Reply: We are moving some of the rendering to the server–so it's true that your server will be doing more work than before. But server costs are constantly going down, and far more powerful than most consumer devices. I think React Server Components will give you the ability to make that tradeoff and choose where you best want the work to be done, on a per component basis. And that's not something that's easily possible today.?
via: 《RFC: React Server Components》
不過以目前我所在的業(yè)務(wù)情況來看,服務(wù)器的成本還是非常貴的,為了降低成本大家紛紛將邏輯下發(fā)到邊緣計(jì)算甚至是客戶端處理。一方面是為了節(jié)省成本,另一方面也是為了降低壓力加快處理。
除了機(jī)器本身的成本之外,請(qǐng)求的成本也會(huì)增加。畢竟除了數(shù)據(jù)請(qǐng)求之外還要處理組件渲染,而且這塊作為組件耦合不好進(jìn)行拆分。相比較常規(guī)方案,使用 JS 文件加載組件到客戶端,接口單純返回?cái)?shù)據(jù),這塊的時(shí)間成本增加了非常多。特別是常規(guī)方案中 JS 文件加載完之后是在瀏覽器中緩存的,后續(xù)的成本非常小。
體積問題可能還好,但是請(qǐng)求時(shí)間增加了這個(gè)可能就非常致命了。
心智負(fù)擔(dān)
這點(diǎn)在 RFC 中也有說明。由于 Server Components 中無法使用 useState, useReduce, useEffect, DOM API 等方法,勢(shì)必這會(huì)給使用者帶來大量的心智負(fù)擔(dān)。雖然官方說會(huì)使用工具讓開發(fā)者做到無感,且會(huì)提供運(yùn)行時(shí)報(bào)錯(cuò),但是我相信光是想什么時(shí)候需要寫 Server Componet 什么時(shí)候需要寫 Client Component 就已經(jīng)腦殼疼了吧,更別提還有個(gè) Shared Component 了。
另外還有就是增加了跨端的流程之后,調(diào)試的成本也會(huì)變的非常高。別說很多人沒有服務(wù)端的經(jīng)驗(yàn),就算是有相關(guān)經(jīng)驗(yàn)的同學(xué)可能也沒辦法很好的在服務(wù)端進(jìn)行快速定位。關(guān)于這個(gè)問題官方提供的說法是可以依賴內(nèi)部的錯(cuò)誤監(jiān)控和日志服務(wù)。
回歸問題的本質(zhì)
讓我們回歸到問題的本質(zhì),React Server Component 的目的其實(shí)是為了解決接口請(qǐng)求分散在各組件中帶來的子組件的數(shù)據(jù)請(qǐng)求需要等待父組件請(qǐng)求完成渲染子組件時(shí)才能開始請(qǐng)求的數(shù)據(jù)請(qǐng)求隊(duì)列問題。那么除了 Server Component 之外沒有其它的解決方案了嗎?其實(shí)不然。
import?React,?{useState,?useEffect}?from?'react';
import?ReactDOM?from?'react-dom';
function?App()?{
??const?[data,?setData]?=?useState([]);
??useEffect(()?=>?{
????fetchData.then(setData);
??},?[]);
??
??return?(
????<div>
??????{!data.length???'loading'?:?null}
??????<Child?data={data}?/>
????div>
??);
}
function?Child({data})?{
??const?[childData,?setData]?=?useState([]);
??useEffect(()?=>?{
????fetchChildData.then(setData);
??},?[]);
??
??if(!data.length)?{
?return?null;
??}
??
??return?(
????<div>{data.length?+?childData.length}div>
??);
}
ReactDOM.render(<App?/>,?document.querySelector('#root'));
如示例代碼所示,只要加載組件,但是在無數(shù)據(jù)情況下不返回 DOM 也是可以做到子組件的數(shù)據(jù)先請(qǐng)求而無需等待的。當(dāng)然這種需要認(rèn)為的在寫法上進(jìn)行優(yōu)化,但我也仍然認(rèn)為比大費(fèi)周章的去做 Server Component 要好很多。
至于 Server Component 帶來的打包體積優(yōu)化這個(gè)問題,我覺得 RFC 里面的評(píng)論說的非常的好?!北绕?83KB(gzip 后大概是 20KB)打包體積,我覺得在項(xiàng)目中為了格式化日期使用一個(gè) 83KB 的庫(kù)這才是更大的問題?!?/p>
Removing a 83KB (20KB gzip) library isn't a big deal, I would say the bigger problem here is that you're using a 83KB library to format dates.?
via: 《RFC: React Server Component》
實(shí)際上官方列舉的兩點(diǎn)關(guān)于日期處理以及 Markdown 格式處理的庫(kù),可以看到都是針對(duì)于數(shù)據(jù)進(jìn)行處理的需求。針對(duì)這種情況如果覺得這塊的體積非?!辟F“的話完全是可以讓服務(wù)端將格式化后的數(shù)據(jù)返回,這樣豈不是更小成本的解決了這個(gè)問題?
后記
看完 《RFC: React Server Component》 中所有的討論,大部分人對(duì) Server Component 還是持不贊成的態(tài)度的,認(rèn)為它可能并沒有像 React Hooks 那樣解決業(yè)務(wù)中的實(shí)際痛點(diǎn)。就目前暴露的提案,我個(gè)人也覺得 Server Component 是弊大于利的。目前就期望官方如果要實(shí)現(xiàn)的話能解耦實(shí)現(xiàn),不要影響未使用 Server Component 的 React 用戶打包體積。
當(dāng)然該提案我覺得不是沒有好處,它最大的好處我個(gè)人認(rèn)為是帶來了 React 組件序列化的官方標(biāo)準(zhǔn)。為多端、多機(jī)、多語言之間實(shí)現(xiàn) React 組件交流提供了基礎(chǔ)?;谶@套序列化方案,我們可以實(shí)現(xiàn)組件緩存存儲(chǔ),多機(jī)器并發(fā)渲染組件等。至于多語言實(shí)現(xiàn)也是在 RFC 討論中大家比較關(guān)心的問題,通過這套序列化標(biāo)準(zhǔn)讓其它語言去實(shí)現(xiàn) React 組件也不是沒有可能。
參考資料
React Server Components: https://reactjs.org/server-components
[2]React Server Components Demo: http://github.com/reactjs/server-components-demo
[3]React Server Comonents RFC: https://github.com/reactjs/rfcs/pull/188
