在服務端寫 React 組件是什麼體驗?

What?在服務端寫 React 組件?這看起來是一件不可思議的事情,首先在服務端,我們有很多的權限,例如訪問文件系統、數據庫等。如果在服務端寫前端組件,是不是意味着 “前端可以直鏈數據庫了?” 還有安全性可言嗎?

本文代碼示例基於 Next.js 框架,讓我們先看一段代碼示例,你可以先思考一個問題,這是服務端代碼還是前端代碼?

// src/app/home/page.tsx
import fs from 'fs/promises';
const DisplayText = async () ={
  try {
    const text = await fs.readFile('/xxx/my-next-app/text.txt'{ encoding: 'utf8' });
    console.log(text); 
    return <p>{text}</p>
  } catch (err) {
    return <p>{err.message}</p>
  }
}


const Home = () ={
  return <div>
    <p>Hello Server Component</p>
    {/* @ts-expect-error */}
    <DisplayText />
  </div>
}


export default Home;

看到這段代碼你的想法是什麼?如果是前端組件代碼,怎麼會有 fs 模塊呢?在前端是沒有文件系統的,更不能訪問本地的文件。如果是後端,看起來怎麼還有前端的組件代碼?

沒什麼神祕的,這是 React 中的 Server Component,簡稱 RSC。它是 React 提出的一種新型組件類型,以前我們編寫組件的方式可以稱爲 Client Component。Server Component 它的渲染是在服務端完成之後通過網絡請求交給客戶端 React 做整合,如果運行時是 Node.js,在 Server Component 中就可以使用 Node.js 中的所有模塊資源,訪問數據庫這些自然就可以了。

它安全嗎?有些代碼你可能只想在服務器上運行,例如在 Server Component 會有一些鏈接數據庫讀取數據的代碼邏輯,但 JavaScript 模塊可以在 Server Component 和 Client Component 之間實現共享,有時你會無意識地在 Client Component 中導入服務器端的代碼,爲了避免敏感的信息被暴露,在 Next.js 框架中,我們可以在文件頭部添加一個 import 'server-only'; 語句,表示該模塊只在服務器端使用。

爲什麼要用 RSC?

React Server Component 帶來了一種新的思維模式,我們要考慮爲什麼使用它?以下是它帶來的一些好處:

這裏有一個重點是要與 SSR 區別開來。SSR 也是在服務端運行,使用它可以解決首屏加載、SEO 問題,它的工作方式是把整個應用程序打包爲 html 然後返回到瀏覽器渲染。React Server Component 返回的是一個客戶端可以解析的 React 結構,之後會在客戶端同 Client Component 混合在做渲染。在 React Server Component 中還可以結合 Streaming、Suspense 允許服務器先返回頁面的部分內容,待一些異步組件稍後就緒時再返回餘下的內容,這也是其強大的一方面。

RSC 流式傳輸

下例代碼中,如果去掉 <Suspense> </Suspense>,React 會等待 <DisplayText /> 組件渲染完成,纔會發送內容到瀏覽器。假設讀取數據很慢,需要 5 秒中,此時頁面就有 5 秒鐘的等待。如果是 SSR 來處理,它的問題也是如此,會在這裏等待,等所有的內容拼接完成後纔會返回。

現在我們可以先讓頁面的其它部分先返回到瀏覽器渲染,Suspense 的 fallback 先渲染第一個版本,待 <DisplayText /> 組件就緒時渲染第二個版本。

import fs from 'fs/promises';
import { Suspense } from 'react';


+ const sleep = (ms: number) => new Promise((resolve, reject) => setTimeout(() => resolve(1), ms))
const DisplayText = async () ={
  try {
+   await sleep(5000);
    const text = await fs.readFile('xxx/my-next-app/text.txt'{ encoding: 'utf8' });
    console.log(text); 
    return <p>{text}</p>
  } catch (err) {
    return <p>{err.message}</p>
  }
}


const Home = () ={
  return <div>
    <p>Hello Server Component</p>
+   <Suspense fallback={<> Loading... </>}>
+     {/* @ts-expect-error */}
+     <DisplayText />
+   </Suspense>
  </div>
}


export default Home;

以下是運行後的預覽效果,剛開始會顯示 Loading...

大約 5 秒鐘後,展示 <DisplayText /> 組件獲取到的內容

什麼情況下使用 RSC?

Next.js 框架 App Router 路由默認開啓 Server Component 模式,它認爲在大多數情況下應該編寫 Server Component 而不是 Client Component。對於開發者而言要有一個邊界,來區分什麼情況下應該使用 Server Component、什麼情況下應該使用 Client Component。

上圖是 Next.js 文檔的一個描述,以下做了一些總結:

應該用 Server Component 的情況:

應該用 Client Component 的情況:

一個 Server、Client Component 示例

現在我們知道了 Server Component 有一些限制,當我們需要寫一些具有交互性的組件時必須在 Client Component 內完成。

以下是一個 Server Component 引入 Client Component 的示例,它們在服務器上預渲染並在客戶端上混合。

// src/app/home/page.tsx
import Count from './Count';
const Home = () ={
  return <div>
    ...
    <Count />
  </div>
}


export default Home;

Server Component 是默認的組件類型,如果要寫 Client Component 需要先在文件頂部引入 'use client' 指令,定義 Client Component 和 Server Component 的邊界。

// src/app/home/Count.tsx
'use client';
import { useState } from "react";
const btnStyle = { padding: '2px 5px', marginLeft: '10px' };
const Count = () ={
  console.log('Client Component');
  const [count, setCount] = useState(0);
  return <div>
    current count: {count}
    <button style={{...btnStyle}} onClick={() => setCount(preCount  => preCount + 1)}> + </button>
    <button style={{...btnStyle}} onClick={() => setCount(preCount  => preCount - 1)}> - </button>
  </div>
};


export default Count;

總結

React Server Component 不是一個新的概念了,第一次提出是在 2020 年 12 月,它提出的這種 Server Component 編程範式,還是挺有趣的,值得學習下。

之前也是大致瞭解,最近看了一些 Next.js 相關的內容,在 2022 年底 Next.js 13 發佈時整合了一些 React Server Component 相關的內容,並在 App Router 將 Server  Component 做爲默認的組件類型,這也是值得關注的一個地方。

在當前前後端分離的背景下,這種前後端寫在一起的方式是不是和以前寫 jadeejs 這種類似?看似回到了以前,但 Server Component 也解決了一些問題,例如減少了 Bundle Size、請求的瀑布流等,並且在服務端也可以使用一些更成熟的 UI 庫來寫 Server Component。

Server Component 的到來,也看到了前端的邊界在不斷的擴大。學習一些 Node.js 和後端相關的知識也是必要的。

Reference

https://scastiel.dev/view-counter-react-server-componentshttps://nextjs.org/docs/getting-started/react-essentialshttps://oldmo860617.medium.com / 從 - next-js-13 - 認識 - react-server-components-37c2bad96d90

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/ossKRpQW6X7eFIc0FOoU9Q