轉載自: 魔術師卡頌 公衆號
12月21日,React
團隊公佈了一個新的提案Server Components
。javascript
伴隨這個提案同時發出的,還有一個小時的視頻講解、可供運行的Demo、詳盡的介紹。html
可見,React
團隊很重視這個提案。本文會從以下方面講解:前端
Server Components
是什麼Server Components
解決了什麼問題一句話歸納:java
Server Components
是在服務端運行的React
組件。git
咦?這和服務端渲染
(SSR)有什麼區別?github
相比SSR
將組件在服務端渲染成填充內容的HTML
字符串,並在客戶端hydrate
後使用。Server Components
更像咱們的在客戶端寫的普通組件同樣,只不過他的運行環境是服務端。數據庫
咱們能夠將組件按照功能分爲:segmentfault
容器組件
交互組件
舉個例子,Note
組件是容器組件
,他負責請求並緩存數據。NoteEditor
是渲染note
數據並執行用戶交互的交互組件
。後端
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 <NoteEditor note={note}/> } }
如例子所述,咱們能夠經過在useEffect
中發起請求並將返回的數據保存在state
中。緩存
這種「請求-渲染」模式會碰見被稱爲waterfall
的問題:
就像一節一節的瀑布往下流水,NoteEditor
須要等待Note
請求note
成功後才能開始渲染。
當交互組件
依賴的數據源越多,waterfall
問題會更明顯。
理論上,若是React
足夠聰明,就能在服務端
執行容器組件
的渲染邏輯,在客戶端
執行交互組件
的渲染邏輯。
按照這樣的理念,以下這棵徹底在客戶端渲染的組件樹:
能夠拆分爲:在服務端
運行的容器組件
和在客戶端
運行的交互組件
。
其中在服務端運行的容器組件
就是Server Component
。
既然ServerComponent
在服務端
運行,自然更接近各類IO
(請求數據庫、讀取文件、緩存...)。
上面的例子徹底能夠直接從數據庫
獲取note
數據,同時藉助Suspense
,採用同步的寫法。
function Note(props) { const note = db.notes.get(props.id); if (note == null) { return "Loading"; } return <NoteEditor note={note}/> }
任何其餘數據源只須要經過React
提供的API
簡單封裝,使其支持Suspense
,就能接入ServerComponent
中。自然更接近後端。
區別於SSR
傳輸的HTML
字符串。ServerComponent
會將Note
組件及其從IO
請求到的數據序列化爲相似JSX
的數據結構,以流
的形式傳遞給前端:
客戶端在運行時直接獲取到填充了數據的流
,並藉助Concurrent Mode
執行流式
渲染。
假設咱們開發一款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
這部分邏輯放在服務端
執行。
ServerComponent
並不會增長前端項目打包體積。這個例子中,一次性爲咱們減小了前端206K (63.3K gzipped)的打包體積以及解析MD
的時間。
經過使用React.lazy
能夠實現組件的動態import
。以前,這須要咱們在切換組件/路由時手動執行。在ServerComponent
中,都是自動完成的。
在上面動圖中,左側列表是ServerComponent
,當點擊其中卡片時,組件對應數據會動態加載。
Vue
做爲一門使用模版語言
的框架,模版語言
的固定寫法使其能在編譯時針對模版內容做出優化。
因爲JSX
僅僅是JS
的語法糖,React
很難在編譯時作出優化。
ServerComponent
對組件提出了更多限制(不能使用useState
、useEffect
...)。這些限制從側面爲AOT
提供更多優化線索。
下面咱們經過改寫一個記事本
組件講解ServerComponent
的使用:
// Note.js import fetchData from './fetchData'; import NoteEditor from './NoteEditor'; function Note(props) { const {id, isEditing} = props; const note = fetchData(id); return ( <div> <h1>{note.title}</h1> <section>{note.body}</section> {isEditing ? <NoteEditor note={note} /> : null } </div> ); }
Note
組件的主要功能是根據props
傳入的id
請求對應的note
數據。
NoteEditor
用於展現及修改note
。
其中fetchData
方法用於獲取數據,數據的加載中狀態由組件外的Suspense
完成。
能夠看到,交互部分由NoteEditor
完成,Note
主要功能是獲取並傳遞數據。
接下來咱們將Note
變爲ServerComponent
。
// 注意🙋 // 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 ( <div> <h1>{note.title}</h1> <section>{note.body}</section> {isEditing ? <NoteEditor note={note} /> : null } </div> ); }
有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
生產環境穩定,讓咱們一塊兒期待2021年吧。
[1] 視頻講解: https://www.youtube.com/watch...
[2] Demo: https://github.com/pomber/ser...