React RFC Server Components是什麼,有啥用

轉載自: 魔術師卡頌 公衆號

12月21日,React團隊公佈了一個新的提案Server Componentsjavascript

伴隨這個提案同時發出的,還有一個小時的視頻講解、可供運行的Demo、詳盡的介紹。html

可見,React團隊很重視這個提案。本文會從以下方面講解:前端

  • Server Components是什麼
  • Server Components解決了什麼問題

ServerComponent是什麼

一句話歸納: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成功後才能開始渲染。

image.png

交互組件依賴的數據源越多,waterfall問題會更明顯。

理論上,若是React足夠聰明,就能在服務端執行容器組件的渲染邏輯,在客戶端執行交互組件的渲染邏輯。

按照這樣的理念,以下這棵徹底在客戶端渲染的組件樹:

image.png

能夠拆分爲:在服務端運行的容器組件和在客戶端運行的交互組件

image.png

其中在服務端運行的容器組件就是Server Component

ServerComponent的意義

既然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中。自然更接近後端。

解決waterfall

區別於SSR傳輸的HTML字符串。ServerComponent會將Note組件及其從IO請求到的數據序列化爲相似JSX的數據結構,以的形式傳遞給前端:

image.png

客戶端在運行時直接獲取到填充了數據的,並藉助Concurrent Mode執行流式渲染。

0打包體積

假設咱們開發一款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,當點擊其中卡片時,組件對應數據會動態加載。

更好的ahead-of-time (AOT)優化

Vue做爲一門使用模版語言的框架,模版語言的固定寫法使其能在編譯時針對模版內容做出優化。

因爲JSX僅僅是JS的語法糖,React很難在編譯時作出優化。

ServerComponent對組件提出了更多限制(不能使用useStateuseEffect...)。這些限制從側面爲AOT提供更多優化線索。

ServerComponent的使用

下面咱們經過改寫一個記事本組件講解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點須要注意的改動,咱們依次瞭解下:

  1. Note.js文件名改成Note.server.js表明這是Server Component
  2. Note.server.js運行於服務端,咱們不須要客戶端的fetchData方法,能夠直接訪問數據庫,因此這裏調用db.server提供的方法
  3. 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...

相關文章
相關標籤/搜索