原文:https://www.builder.io/blog/nextjs-react-server-components
作者:VISHWAS GOPINATH
译者:ChatGPT 4 Turbo
随着 React 19 和 Next.js 15 的稳定版本发布临近,React 团队介绍了 React 架构中最重要的变化之一:React 服务器组件(RSC)范式,它区分了服务器组件和客户端组件。
服务器组件 只在服务器上运行,对包大小没有任何影响。它们的代码永远不会被下载到客户端,有助于减少包大小并提升启动时间。
相比之下,客户端组件 是您已经习惯的典型组件。它们可以访问 React 的全部功能:状态、效果、访问 DOM 等。
这种新方法旨在利用服务器和客户端环境的优势,优化效率、加载时间和交互性。
尽管它为 React 开发带来了重大改进和灵活性,但它也引入了一些容易被误解的概念。在这篇文章中,我们将解释关于 React 服务器组件(RSC)的五个常见误解,以帮助阐明这种新架构的一些细节问题。
误解 1:应该优先选择服务器组件,而客户端组件应该谨慎使用
您可能会认为,为了从新的 RSC 架构中获得最多的好处,您应该将所有组件转换为服务器组件,并谨慎使用客户端组件。然而,最优的方法实际上是更加平衡的:
- 虽然服务器组件提供了很多好处,但它们并不适合所有场景,特别是那些需要交互性的场景。React 服务器组件是一种新型的 React 组件,只在服务器上操作,获取数据并完全在服务器上渲染以提高性能。
// UserProfile.js (服务器组件)
// 该组件可以直接访问数据库或 API
// 而不将敏感信息暴露给客户端
async function UserProfile({ userId }) {
const response = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
const user = response.user;
return (
<div>
<h1>{user.name}</h1>
<p>电子邮件:{user.email}</p>
<p>位置:{user.location}</p>
<LikeButton initialLikes={user.likes} />
</div>
);
}
- 客户端组件对于需要客户端交互性、状态管理或浏览器 API 的功能来说是必不可少的。
// LikeButton.js(客户端组件)
// 此组件可以处理交互
'use client'
import { useState } from 'react';
function LikeButton({ initialLikes }) {
const [likes, setLikes] = useState(initialLikes);
const handleLike = () => {
setLikes(likes + 1);
};
return (
<button onClick={handleLike}>
点赞({likes})
</button>
);
}
- 推荐的做法是将客户端组件放在组件树尽可能低的位置,理想情况下使它们成为叶子组件。
引入服务器组件并不意味着它们本质上优于客户端组件,或者应该始终被优先选择。两者在一个结构良好的 React 应用程序中各有其位置。客户端组件对于创建交互式、动态用户界面至关重要,在适当的地方使用它们不仅是可接受的,而且往往是必要的。
请记住,这种新架构的目标是为构建高效的 React 应用程序提供更多工具,而不是完全替代客户端组件。在合适的地方使用服务器组件,不要犹豫在满足您需求的地方使用客户端组件。
误解 2:use client
标记一个组件为客户端组件,而 use server
标记一个组件为服务器组件
你可能会认为我们声明服务器和客户端组件的方式有一种对称性。新架构旨在支持 React 服务器组件,允讱它们仅在服务器上运行并利用服务器端渲染的好处。实际情况有些不同:
use client
确实标记了客户端组件的边界。
// LikeButton.js - 'use client' 让你标记哪些代码在客户端运行
'use client'
function LikeButton({ initialLikes }) {
// 组件的其余代码
}
- 然而,对于标记服务器组件,并没有相应的
use server
指令。在 Next.js App Router和 React 服务器组件范式中,服务器组件是默认选项。
// UserProfile.js - 默认为服务器组件
async function UserProfile({ userId }) {
// 组件代码的其余部分
}
这种区别从根本上影响了你组织和构建 React 应用的方式。通过将服务器组件设为默认,React 鼓励采取“服务器优先”的方法,允许你在不需要额外努力的情况下利用服务器端渲染的优势。你只需要明确标记客户端组件,这有助于快速识别需要客户端交互的部分。这种方法可以带来更好的初始加载时间和减少客户端 JavaScript,优化应用的性能。
值得注意的是,确实存在一个
use server
指令,但它用于不同的目的。它用于标记 Server Action,而不是声明服务器组件。
误解 3:服务器组件总是在服务器上渲染,而客户端组件总是在浏览器中渲染
乍一看,你可能会认为服务器和客户端组件的渲染位置有一个明确的区分。传统的 React 组件通常在客户端渲染,但随着服务器和客户端组件的引入,渲染过程变得更加细致:
- 服务器组件确实在服务器上渲染。
- 然而,许多人没有意识到,客户端组件比较多功能。它们可以在服务器(用于初始页面加载)和客户端上渲染。
- 关键区别在于它们的功能:客户端组件可以使用客户端特性,如状态、效果和事件处理程序,而服务器组件则不能。
仅依赖客户端 JavaScript 渲染网页会带来重大不利。用户最初会看到一个空白页面,直到他们的浏览器下载、处理并运行 JavaScript 代码,这导致了内容可见性的延迟。这种延迟会让网站感觉缓慢和无响应,特别是在网络较慢或设备性能较差的情况下。此外,它还可能阻碍搜索引擎索引,可能影响网站在搜索结果中的可见度。
为了优化这一点,你可以在服务器上为初始页面加载渲染客户端组件。这种预渲染的 HTML 允许用户立即看到内容,同时 React 在后台工作,使组件完全交互,对用户来说没有任何可见的变化。这种方法显著提高了应用程序的感知加载速度和实际性能。
误解 4:每个具有交互性的组件都需要自己的 use client
指令
你可能会认为每个具有任何客户端行为的组件都需要自己的 use client
指令。然而,事实并非如此:
- 你只需要在文件顶部为最顶层需要客户端功能的组件添加
use client
。 - 该文件中定义的所有组件自动成为客户端组件。
- 这包括文件中的嵌套组件、实用工具和函数。
在下面的组件树中,最初是服务器组件的 FormatText.js
,当导入到标有 use client
指令的客户端组件 Dashboard.js
时,会在客户端执行。(白色代表服务器组件,蓝色代表客户端组件。)
为什么理解这一点很重要?
由于 JavaScript 模块可以在服务器和客户端组件之间共享,因此只打算用于服务器的代码可能最终在客户端运行。
考虑这个数据获取函数:
export async function getData() {
const res = await fetch('https://external-service.com/data', {
headers: {
authorization: process.env.API_KEY,
},
})
return res.json()
}
虽然它在服务器和客户端上都能正常工作,但它包含了一个仅供服务器端使用的 API_KEY
。如果这个函数被导入到客户端组件中,可能会暴露敏感信息。通过将敏感数据和逻辑,例如令牌和 API 密钥,保留在服务器上,服务器组件增强了应用程序的安全性。
Next.js 将私有环境变量(那些没有以 NEXT_PUBLIC
为前缀的)在客户端替换为一个空字符串,以防止泄露。这意呈着如果在客户端执行 getData()
,它将不会按预期工作。
理解这些细微差别有助于您更安全、更有效地构建您的应用程序。通过注意您的代码运行位置,您可以防止敏感信息泄露,并确保您的组件无论是在服务器还是客户端上都能按预期工作。
误解 5:服务器组件不能嵌套在客户端组件内
你可能会认为,一旦你进入了客户端组件领域,就不能回头,因此服务器组件不能嵌套在客户端组件内。但实际上比你想象的更有弹性:
- 虽然你不能直接在客户端组件内嵌套服务器组件,但你可以将服务器组件作为 props 传递给客户端组件。这种方式允许客户端和服务器组件的无缝整合,优化性能和互动性。
- 一个常见的模式被称为 “children as props” 模式。这里有一个简单的例子:
// ServerComponent.js
function ServerComponent() {
return <h1>Hello from the server</h1>;
}
// ClientComponent.js
'use client'
function ClientComponent({ children }) {
return (
<div>
<p>This is a client component</p>
{children}
</div>
);
}
// Usage
function App() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
);
}
这种灵活性使您能够通过将数据获取和大量计算保留在服务器上,同时仍然在需要的地方提供交互性,从而创建更高效的应用程序。这就像拥有两全其美的情况 – 服务器端操作的强大力量与客户端交互性的响应性。
React 服务器组件的引入并不意味着你需要在一夜之间彻底改变你构建 React 应用的方式。相反,将其视为你开发者工具包中的一个强大新工具,一个你可以逐渐采纳并实验的工具,以优化你的 React 应用程序。