伺服器元件
伺服器元件是一種新型的元件,它會在打包之前,在與客戶端應用程式或 SSR 伺服器分離的環境中預先渲染。
這個獨立的環境就是 React 伺服器元件中的「伺服器」。伺服器元件可以在建置時在 CI 伺服器上執行一次,也可以使用網路伺服器針對每個請求執行。
沒有伺服器的伺服器元件
伺服器元件可以在建置時執行以從檔案系統讀取或擷取靜態內容,因此不需要網路伺服器。例如,您可能想要從內容管理系統讀取靜態資料。
如果沒有伺服器元件,通常會使用 Effect 在客戶端上擷取靜態資料
// bundle.js
import marked from 'marked'; // 35.9K (11.2K gzipped)
import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped)
function Page({page}) {
const [content, setContent] = useState('');
// NOTE: loads *after* first page render.
useEffect(() => {
fetch(`/api/content/${page}`).then((data) => {
setContent(data.content);
});
}, [page]);
return <div>{sanitizeHtml(marked(content))}</div>;
}
// api.js
app.get(`/api/content/:page`, async (req, res) => {
const page = req.params.page;
const content = await file.readFile(`${page}.md`);
res.send({content});
});
這種模式意味著使用者需要下載並解析額外的 75K(壓縮後)程式庫,並在頁面載入後等待第二個請求來擷取資料,僅為了渲染在頁面生命週期內不會更改的靜態內容。
使用伺服器元件,您可以在建置時將這些元件渲染一次
import marked from 'marked'; // Not included in bundle
import sanitizeHtml from 'sanitize-html'; // Not included in bundle
async function Page({page}) {
// NOTE: loads *during* render, when the app is built.
const content = await file.readFile(`${page}.md`);
return <div>{sanitizeHtml(marked(content))}</div>;
}
然後可以將渲染的輸出伺服器端渲染 (SSR) 為 HTML 並上傳到 CDN。當應用程式載入時,客戶端將不會看到原始的 Page
元件,或用於渲染 Markdown 的昂貴程式庫。客戶端只會看到渲染的輸出
<div><!-- html for markdown --></div>
這表示內容在首頁載入期間可見,並且軟體包不包含渲染靜態內容所需的昂貴程式庫。
具有伺服器的伺服器元件
伺服器元件也可以在頁面請求期間在網路伺服器上執行,讓您可以存取資料層而無需建置 API。它們會在您的應用程式打包之前進行渲染,並且可以將資料和 JSX 作為 props 傳遞給客戶端元件。
如果沒有伺服器元件,通常會在 Effect 中於客戶端上擷取動態資料
// bundle.js
function Note({id}) {
const [note, setNote] = useState('');
// NOTE: loads *after* first render.
useEffect(() => {
fetch(`/api/notes/${id}`).then(data => {
setNote(data.note);
});
}, [id]);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
function Author({id}) {
const [author, setAuthor] = useState('');
// NOTE: loads *after* Note renders.
// Causing an expensive client-server waterfall.
useEffect(() => {
fetch(`/api/authors/${id}`).then(data => {
setAuthor(data.author);
});
}, [id]);
return <span>By: {author.name}</span>;
}
// api
import db from './database';
app.get(`/api/notes/:id`, async (req, res) => {
const note = await db.notes.get(id);
res.send({note});
});
app.get(`/api/authors/:id`, async (req, res) => {
const author = await db.authors.get(id);
res.send({author});
});
使用伺服器元件,您可以讀取資料並在元件中進行渲染
import db from './database';
async function Note({id}) {
// NOTE: loads *during* render.
const note = await db.notes.get(id);
return (
<div>
<Author id={note.authorId} />
<p>{note}</p>
</div>
);
}
async function Author({id}) {
// NOTE: loads *after* Note,
// but is fast if data is co-located.
const author = await db.authors.get(id);
return <span>By: {author.name}</span>;
}
然後,打包器會將資料、渲染的伺服器元件和動態客戶端元件組合成一個軟體包。或者,可以將該軟體包伺服器端渲染 (SSR) 以建立頁面的初始 HTML。當頁面載入時,瀏覽器不會看到原始的 Note
和 Author
元件;只有渲染的輸出會傳送至客戶端
<div>
<span>By: The React Team</span>
<p>React 19 is...</p>
</div>
伺服器元件可以透過從伺服器重新提取它們來實現動態化,在伺服器上它們可以訪問數據並重新渲染。這種新的應用程式架構結合了以伺服器為中心的「多頁應用程式」簡單的「請求/響應」心智模型,以及以客戶端為中心的「單頁應用程式」的無縫互動性,讓您兩全其美。
為伺服器元件添加互動性
伺服器元件不會發送到瀏覽器,因此它們不能使用像 useState
這樣的互動式 API。要為伺服器元件添加互動性,您可以使用 "use client"
指令將它們與客戶端元件組合。
在以下範例中,Notes
伺服器元件導入了一個 Expandable
客戶端元件,該元件使用狀態來切換其 expanded
狀態。
// Server Component
import Expandable from './Expandable';
async function Notes() {
const notes = await db.notes.getAll();
return (
<div>
{notes.map(note => (
<Expandable key={note.id}>
<p note={note} />
</Expandable>
))}
</div>
)
}
// Client Component
"use client"
export default function Expandable({children}) {
const [expanded, setExpanded] = useState(false);
return (
<div>
<button
onClick={() => setExpanded(!expanded)}
>
Toggle
</button>
{expanded && children}
</div>
)
}
這是透過首先將 Notes
渲染為伺服器元件,然後指示打包器為客戶端元件 Expandable
建立一個捆綁包來實現的。在瀏覽器中,客戶端元件會將伺服器元件的輸出作為屬性傳遞。
<head>
<!-- the bundle for Client Components -->
<script src="bundle.js" />
</head>
<body>
<div>
<Expandable key={1}>
<p>this is the first note</p>
</Expandable>
<Expandable key={2}>
<p>this is the second note</p>
</Expandable>
<!--...-->
</div>
</body>
具有伺服器元件的非同步元件
伺服器元件引入了一種使用 async/await 編寫元件的新方法。當您在非同步元件中使用 await
時,React 將暫停並等待 promise 解析後再繼續渲染。這適用於跨伺服器/客戶端邊界,並支援 Suspense 串流。
您甚至可以在伺服器上建立一個 promise,然後在客戶端上等待它。
// Server Component
import db from './database';
async function Page({id}) {
// Will suspend the Server Component.
const note = await db.notes.get(id);
// NOTE: not awaited, will start here and await on the client.
const commentsPromise = db.comments.get(note.id);
return (
<div>
{note}
<Suspense fallback={<p>Loading Comments...</p>}>
<Comments commentsPromise={commentsPromise} />
</Suspense>
</div>
);
}
// Client Component
"use client";
import {use} from 'react';
function Comments({commentsPromise}) {
// NOTE: this will resume the promise from the server.
// It will suspend until the data is available.
const comments = use(commentsPromise);
return comments.map(commment => <p>{comment}</p>);
}
note
內容是頁面渲染的重要數據,因此我們在伺服器上 await
它。註釋位於下方且優先順序較低,因此我們在伺服器上啟動 promise,並使用 use
API 在客戶端上等待它。這將在客戶端上暫停,而不會阻止 note
內容渲染。
由於 客戶端不支援非同步元件,因此我們使用 use
等待 promise。