React 服务器组件 - This feature is available in the latest Canary

服务器组件是一种新型组件,在打包之前,在与客户端应用或 SSR 服务器分开的环境中提前渲染。

¥Server Components are a new type of Component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server.

这个单独的环境是 React Server Components 中的 “server”。服务器组件可以在 CI 服务器上构建时运行一次,也可以使用 Web 服务器针对每个请求运行。

¥This separate environment is the “server” in React Server Components. Server Components can run once at build time on your CI server, or they can be run for each request using a web server.

注意

如何构建对服务器组件的支持?

¥How do I build support for Server Components?

虽然 React 19 中的 React Server Components 是稳定的,不会在主要版本之间中断,但用于实现 React Server Components 打包器或框架的底层 API 不遵循 semver,并且可能会在 React 19.x 中的次要版本之间中断。

¥While React Server Components in React 19 are stable and will not break between major versions, the underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x.

要支持 React Server Components 作为打包器或框架,我们建议固定到特定的 React 版本,或使用 Canary 版本。我们将继续与打包器和框架合作,以稳定未来用于实现 React 服务器组件的 API。

¥To support React Server Components as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement React Server Components in the future.

没有服务器的服务器组件

¥Server Components without a Server

服务器组件可以在构建时运行以从文件系统读取或获取静态内容,因此不需要 Web 服务器。例如,你可能想要从内容管理系统中读取静态数据。

¥Server components can run at build time to read from the filesystem or fetch static content, so a web server is not required. For example, you may want to read static data from a content management system.

如果没有服务器组件,通常会在效果中获取客户端上的静态数据:

¥Without Server Components, it’s common to fetch static data on the client with an 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(gzip 压缩)库,并等待页面加载后的第二个请求来获取数据,只是为了渲染在页面生命周期内不会改变的静态内容。

¥This pattern means users need to download and parse an additional 75K (gzipped) of libraries, and wait for a second request to fetch the data after the page loads, just to render static content that will not change for the lifetime of the page.

使用服务器组件,你可以在构建时渲染这些组件一次:

¥With Server Components, you can render these components once at build time:

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 的昂贵库。客户端只会看到渲染的输出:

¥The rendered output can then be server-side rendered (SSR) to HTML and uploaded to a CDN. When the app loads, the client will not see the original Page component, or the expensive libraries for rendering the markdown. The client will only see the rendered output:

<div><!-- html for markdown --></div>

这意味着内容在第一次页面加载期间可见,并且包不包含渲染静态内容所需的昂贵库。

¥This means the content is visible during first page load, and the bundle does not include the expensive libraries needed to render the static content.

注意

你可能会注意到上面的服务器组件是一个异步函数:

¥You may notice that the Server Component above is an async function:

async function Page({page}) {
//...
}

异步组件是服务器组件的一项新功能,允许你在渲染中使用 await

¥Async Components are a new feature of Server Components that allow you to await in render.

参见下面的 使用服务器组件的异步组件

¥See Async components with Server Components below.

有服务器的服务器组件

¥Server Components with a Server

服务器组件还可以在请求页面期间在 Web 服务器上运行,让你无需构建 API 即可访问数据层。它们在你的应用打包之前渲染,并且可以将数据和 JSX 作为属性传递给客户端组件。

¥Server Components can also run on a web server during a request for a page, letting you access your data layer without having to build an API. They are rendered before your application is bundled, and can pass data and JSX as props to Client Components.

如果没有服务器组件,通常会在效果中获取客户端上的动态数据:

¥Without Server Components, it’s common to fetch dynamic data on the client in an 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});
});

使用服务器组件,你可以读取数据并在组件中渲染它:

¥With Server Components, you can read the data and render it in the component:

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。当页面加载时,浏览器看不到原始 NoteAuthor 组件;只有渲染的输出才会发送到客户端:

¥The bundler then combines the data, rendered Server Components and dynamic Client Components into a bundle. Optionally, that bundle can then be server-side rendered (SSR) to create the initial HTML for the page. When the page loads, the browser does not see the original Note and Author components; only the rendered output is sent to the client:

<div>
<span>By: The React Team</span>
<p>React 19 is...</p>
</div>

可以通过从服务器重新获取服务器组件来实现动态化,在服务器中,它们可以访问数据并再次渲染。这种新的应用架构将以服务器为中心的多页应用的简单“请求/响应”思维模型与以客户端为中心的单页应用的无缝交互性相结合,为你提供两全其美的效果。

¥Server Components can be made dynamic by re-fetching them from a server, where they can access the data and render again. This new application architecture combines the simple “request/response” mental model of server-centric Multi-Page Apps with the seamless interactivity of client-centric Single-Page Apps, giving you the best of both worlds.

为服务器组件添加交互性

¥Adding interactivity to Server Components

服务器组件不会发送到浏览器,因此它们无法使用 useState 等交互式 API。要为服务器组件添加交互性,你可以使用 "use client" 指令将它们与客户端组件组合在一起。

¥Server Components are not sent to the browser, so they cannot use interactive APIs like useState. To add interactivity to Server Components, you can compose them with Client Component using the "use client" directive.

注意

没有用于服务器组件的指令。

¥There is no directive for Server Components.

一个常见的误解是服务器组件用 "use server" 表示,但没有针对服务器组件的指令。"use server" 指令用于服务器操作。

¥A common misunderstanding is that Server Components are denoted by "use server", but there is no directive for Server Components. The "use server" directive is used for Server Actions.

有关更多信息,请参阅 指令 的文档。

¥For more info, see the docs for Directives.

在以下示例中,Notes 服务器组件导入使用状态切换其 expanded 状态的 Expandable 客户端组件:

¥In the following example, the Notes Server Component imports an Expandable Client Component that uses state to toggle its expanded state:

// 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 创建打包包来实现。在浏览器中,客户端组件将看到作为属性传递的服务器组件的输出:

¥This works by first rendering Notes as a Server Component, and then instructing the bundler to create a bundle for the Client Component Expandable. In the browser, the Client Components will see output of the Server Components passed as props:

<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 components with Server Components

服务器组件引入了一种使用 async/await 编写组件的新方法。当你在异步组件中 await 时,React 将暂停并等待 promise 解决后再恢复渲染。这可以跨越服务器/客户端边界,并为 Suspense 提供流式支持。

¥Server Components introduce a new way to write Components using async/await. When you await in an async component, React will suspend and wait for the promise to resolve before resuming rendering. This works across server/client boundaries with streaming support for Suspense.

你甚至可以在服务器上创建一个 promise,并在客户端上等待它:

¥You can even create a promise on the server, and await it on the client:

// 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 内容渲染。

¥The note content is important data for the page to render, so we await it on the server. The comments are below the fold and lower-priority, so we start the promise on the server, and wait for it on the client with the use API. This will Suspend on the client, without blocking the note content from rendering.

由于异步组件是 客户端不支持,我们使用 use 等待 promise。

¥Since async components are not supported on the client, we await the promise with use.


React 中文网 - 粤ICP备13048890号