新聞中心
12月21日,React團隊公布了一個新的提案Server Components。

在襄陽等地區(qū),都構建了全面的區(qū)域性戰(zhàn)略布局,加強發(fā)展的系統(tǒng)性、市場前瞻性、產品創(chuàng)新能力,以專注、極致的服務理念,為客戶提供成都做網站、網站建設 網站設計制作定制網站建設,公司網站建設,企業(yè)網站建設,品牌網站建設,成都營銷網站建設,外貿網站制作,襄陽網站建設費用合理。
伴隨這個提案同時發(fā)出的,還有一個小時的視頻講解[1]、可供運行的Demo[2]、詳盡的介紹。
可見,React團隊很重視這個提案。本文會從如下方面講解:
- Server Components是什么
- Server Components解決了什么問題
ServerComponent是什么
一句話概括:
Server Components是在服務端運行的React組件。
咦?這和服務端渲染(SSR)有什么區(qū)別?
相比SSR將組件在服務端渲染成填充內容的HTML字符串,并在客戶端hydrate后使用。Server Components更像我們的在客戶端寫的普通組件一樣,只不過他的運行環(huán)境是服務端。
我們可以將組件按照功能分為:
- 提供數據的容器組件
- 渲染數據并提供數據交互的交互組件
舉個例子,Note組件是容器組件,他負責請求并緩存數據。NoteEditor是渲染note數據并執(zhí)行用戶交互的交互組件。
- function Note(props) {
- const [note, setNote] = useState(null);
- useEffect(() => {
- fetchNote(props.id).then(noteData => {
- setNote(noteData);
- });
- }, [props.id]);
- if (note == null) {
- return "Loading";
- } else {
- return
- }
- }
如例子所述,我們可以通過在useEffect中發(fā)起請求并將返回的數據保存在state中。
這種「請求-渲染」模式會遇見被稱為waterfall的問題:
就像一節(jié)一節(jié)的瀑布往下流水,NoteEditor需要等待Note請求note成功后才能開始渲染。
當交互組件依賴的數據源越多,waterfall問題會更明顯。
理論上,如果React足夠聰明,就能在服務端執(zhí)行容器組件的渲染邏輯,在客戶端執(zhí)行交互組件的渲染邏輯。
按照這樣的理念,如下這棵完全在客戶端渲染的組件樹:
可以拆分為:在服務端運行的容器組件和在客戶端運行的交互組件。
其中在服務端運行的容器組件就是Server Component。
ServerComponent的意義
既然ServerComponent在服務端運行,天然更接近各種IO(請求數據庫、讀取文件、緩存...)。
上面的例子完全可以直接從數據庫獲取note數據,同時借助Suspense,采用同步的寫法。
- function Note(props) {
- const note = db.notes.get(props.id);
- if (note == null) {
- return "Loading";
- }
- return
- }
天然更接近后端
任何其他數據源只需要通過React提供的API簡單封裝,使其支持Suspense,就能接入ServerComponent中。天然更接近后端。
解決waterfall
區(qū)別于SSR傳輸的HTML字符串。ServerComponent會將Note組件及其從IO請求到的數據序列化為類似JSX的數據結構,以流的形式傳遞給前端:
客戶端在運行時直接獲取到填充了數據的流,并借助Concurrent Mode執(zhí)行流式渲染。
0打包體積
假設我們開發(fā)一款MD編輯器。服務端傳遞給前端MD格式的字符串。
我們需要在前端引入將MD解析為HTML字符串的庫。這個庫就有206k。
- 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 */);
- }
通過ServerComponent我們怎么解決這個問題呢?
只需要簡單將NoteWithMarkdown標記為ServerComponent,將引入并解析MD這部分邏輯放在服務端執(zhí)行。
ServerComponent并不會增加前端項目打包體積。這個例子中,一次性為我們減少了前端206K (63.3K gzipped)的打包體積以及解析MD的時間。
自動代碼分割
通過使用React.lazy可以實現組件的動態(tài)import。之前,這需要我們在切換組件/路由時手動執(zhí)行。在ServerComponent中,都是自動完成的。
在上面動圖中,左側列表是ServerComponent,當點擊其中卡片時,組件對應數據會動態(tài)加載。
更好的ahead-of-time (AOT)優(yōu)化
Vue作為一門使用模版語言的框架,模版語言的固定寫法使其能在編譯時針對模版內容作出優(yōu)化。
由于JSX僅僅是JS的語法糖,React很難在編譯時做出優(yōu)化。
ServerComponent對組件提出了更多限制(不能使用useState、useEffect...)。這些限制從側面為AOT提供更多優(yōu)化線索。
ServerComponent的使用
下面我們通過改寫一個記事本組件講解ServerComponent的使用:
- // Note.js
- import fetchData from './fetchData';
- import NoteEditor from './NoteEditor';
- function Note(props) {
- const {id, isEditing} = props;
- const note = fetchData(id);
- return (
{note.title}
{note.body} - {isEditing
- ?
- : null
- }
- );
- }
Note組件的主要功能是根據props傳入的id請求對應的note數據。
NoteEditor用于展示及修改note。
其中fetchData方法用于獲取數據,數據的加載中狀態(tài)由組件外的Suspense完成。
可以看到,交互部分由NoteEditor完成,Note主要功能是獲取并傳遞數據。
接下來我們將Note變?yōu)镾erverComponent。
- // 注意
- // Note.server.js - Server Component
- // 注意
- import db from 'db.server';
- // 注意
- import NoteEditor from './NoteEditor.client';
- function Note(props) {
- const {id, isEditing} = props;
- const note = db.posts.get(id);
- return (
{note.title}
{note.body} - {isEditing
- ?
- : null
- }
- );
- }
有3點需要注意的改動,我們依次了解下:
- Note.js文件名改為Note.server.js代表這是Server Component。
- Note.server.js運行于服務端,我們不需要客戶端的fetchData方法,可以直接訪問數據庫,所以這里調用db.server提供的方法
- NoteEditor用于展示及修改note。這是由客戶端用戶的交互控制的,所以將文件名改為NoteEditor.client代表這是個Client Component。
總結
太陽底下沒有新鮮事。早期前端交互簡單,僅僅作為服務端的View層。
隨著前端交互變復雜,出現了前端框架主導的客戶端渲染(CSR)。
為了解決首屏渲染速度、SEO問題,出現了服務端渲染(SSR),又回到了曾經作為View層的起點,只不過控制的粒度更細。
ServerComponent提案的出現,預示著React的長遠目標:將對View層的控制細化到組件級別。
為什么是「長遠目標」?ServerComponent落地的大前提是Concurrent Mode生產環(huán)境穩(wěn)定,讓我們一起期待2021年吧。
參考資料
[1]視頻講解:https://www.youtube.com/watch?v=TQQPAU21ZUw
[2]Demo:https://github.com/pomber/server-components-demo/
文章標題:ReactRFCServerComponents是什么,有啥用?
文章分享:http://www.dlmjj.cn/article/dpcopcp.html


咨詢
建站咨詢
