了解服务器/客户端组件
在 Next.js App Router 中,所有组件默认都是服务器组件。 但有时您需要客户端功能(如 useState、onClick 等)。如何实现?
要使用客户端组件,可以在文件顶部添加"use client "
指令。
使用客户端
export const ClientComponent = () => {
返回 (
<div> <h1>客户端组件</h1
<h1>客户端组件</h1
</div> </div
);
};
这将从该组件创建一个客户端边界,因此该组件导入的所有组件和模块文件都将在客户端运行。 (请注意,重要的是导入关系,而不是组件的呈现层次)。
因此,即使不是"使用客户端 "的
组件也会自动转换为客户端组件。
像这样的情况怎么办?让我们在客户端组件中声明一个服务器组件
import React from 'react';
export const ServerComponent = async () => {
const data = await getData();
返回 <div>{JSON.stringify(data)}</div>;
};
async 函数 getData() {
const res = await fetch('<https://jsonplaceholder.typicode.com/todos/1>');
const json = await res.json();
if (!res.ok) {
throw new Error('Failed to fetch data');
}
} return json;
}
export default ServerComponent;
使用客户端
从'./ServerComponent'导入 ServerComponent
export const ClientComponent = () => {
返回 (
<div> <h1>客户端组件</h1
<h1>客户端组件</h1
<ServerComponent /> <div
</div> </div
)
}
你应该会看到一个错误日志,其中包含如下警告
在同时使用客户端组件和服务器组件时有一些重要规则,例如
- 客户端组件位于服务器组件之下(可能)
- 客户端组件作为客户端组件的子组件(可以)
- ❌ 客户端组件下的服务器组件(不可能)
解决方法:利用道具模式
那么如何在客户端组件中使用服务器组件呢? 答案很简单:将其作为道具传递。
// 操作模式
使用客户端
export const ClientComponent = ({ children }) => {
返回 (
<div> <h1>客户端组件
<h1>客户端组件</h1
{children}
</div> </div
);
};
const App = () => {
返回 (
<ClientComponent> <ServerComponent
<ServerComponent />.
</ClientComponent> </ServerComponent
)
}
为什么这样做可行?
关键在于客户端边界(Client Boundary)是根据导入位置而不是组件的父子关系确定的。 在上面的例子中,ServerComponent
仍然是一个服务器组件,因为它是从 App 中导入的,而 App 是一个服务器组件。
ClientComponent
只知道它作为道具接收的子组件将在哪里呈现,而不知道它们来自哪个组件。 这种模式允许使用服务器组件,即使它被封装在一个在布局中声明使用客户端的提供程序中。
深入探讨
服务器组件的工作方式与传统的服务器端呈现(SSR)完全不同。 SSR 会在服务器上生成完成的 HTML,而服务器组件则不同,它会生成一种特殊类型的类似 JSON 的流,称为 React Server Components (RSC) Payload。
这种 RSC 有效负载包含组件树结构、数据和 HTML 内容,服务器会序列化字符串、数字和数组等基本数据类型,以及 React 元素、Date 对象或 Map 和 Set 对象等内置对象,以便将它们包含在这种有效负载中。 另一方面,函数、类实例、闭包和事件监听器等对象无法序列化。
因此,你不能把不能序列化的值作为道具
从服务器组件传递到客户端组件!
当服务器组件在呈现过程中遇到客户端组件时,该部分会被标记为一个特殊的占位符。 该占位符包含客户端组件的引用和道具信息,以便客户端能正确呈现该组件。
客户端会将从服务器接收到的 RSC 有效负载转换成 React 可以理解的形式,并将从服务器接收到的内容水合。 客户端会在占位符被标记的地方呈现相应的客户端组件。
请参见