Next 服务器组件和客户端组件介绍
前言
服务端组件和客户端组件是 Next.js 中非常重要的概念。如果没有细致的了解过,你可能会简单的以为所谓服务端组件就是 SSR,客户端组件就是 CSR,服务端组件在服务端进行渲染,客户端组件在客户端进行渲染等等,实际上并非如此。本篇就让我们深入学习和探究 Next.js 的双组件模型吧!
服务端组件
介绍
在 Next.js 中 组件默认就是服务端组件
举个例子 新建一个pages/todo/index.js
文件
export default async function Page() {
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
const data = (await res.json()).slice(0, 10);
console.log(data);
return (
<ul>
{data.map(({ title, id }) => {
return <li key={id}>{title}</li>;
})}
</ul>
);
}
请求会在服务端执行,并将渲染后的 HTML 发送给客户端:
因为在服务端执行,console 打印的结果也只可能会出现在命令行中,而非客户端浏览器中。
优势
数据获取:通常服务端环境(网络、性能等)更好,离数据源更近,在服务端获取数据会更快。通过减少数据加载时间以及客户端发出的请求数量来提高性能
安全:在服务端保留敏感数据和逻辑,不用担心暴露给客户端
缓存:服务端渲染的结果可以在后续的请求中复用,提高性能
bundle 大小:服务端组件的代码不会打包到 bundle 中,减少了 bundle 包的大小
初始页面加载和 FCP:服务端渲染生成 HTML,快速展示 UI
Streaming:服务端组件可以将渲染工作拆分为 chunks,并在准备就绪时将它们流式传输到客户端。用户可以更早看到页面的部分内容,而不必等待整个页面渲染完毕
因为服务端组件的诸多好处,在实际项目开发的时候,能使用服务端组件就尽可能使用服务端组件
。
限制
虽然使用服务端组件有很多好处,但使用服务端组件也有一些限制,比如不能使用 useState
管理状态,不能使用浏览器的 API 等等。如果我们使用了 Next.js
会报错,比如我们将代码修改为:
import { useState } from "react";
export default async function Page() {
const [title, setTitle] = useState("");
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
const data = (await res.json()).slice(0, 10);
console.log(data);
return (
<ul>
{data.map(({ title, id }) => {
return <li key={id}>{title}</li>;
})}
</ul>
);
}
- 此时浏览器会报错
报错提示我们此时需要使用客户端组件。那么又该如何使用客户端组件呢?
客户端组件
介绍
使用客户端组件,你需要在文件顶部添加一个 use client
声明,修改 app/todo/page.js
,代码如下:
"use client";
import { useEffect, useState } from "react";
function getRandomInt(min, max) {
const minCeiled = Math.ceil(min);
const maxFloored = Math.floor(max);
return Math.floor(Math.random() * (maxFloored - minCeiled) + minCeiled);
}
export default function Page() {
const [list, setList] = useState([]);
const fetchData = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/todos");
const data = (await res.json()).slice(0, getRandomInt(1, 10));
setList(data);
};
useEffect(() => {
fetchData();
}, []);
return (
<>
<ul>
{list.map(({ title, id }) => {
return <li key={id}>{title}</li>;
})}
</ul>
<button
onClick={() => {
location.reload();
}}
>
换一批
</button>
</>
);
}
在这个例子中,我们使用了 useEffect
、useState
等 React API,也给按钮添加了点击事件、使用了浏览器的 API。无论使用哪个都需要先声明为客户端组件。
注意
注意:use client
用于声明服务端和客户端组件模块之间的边界。当你在文件中定义了一个 use client
,导入的其他模块包括子组件,都会被视为客户端 bundle 的一部分。
优势
交互性:客户端组件可以使用 state、effects 和事件监听器,意味着用户可以与之交互
浏览器 API:客户端组件可以使用浏览器 API 如地理位置、localStorage 等
服务端组件 VS 客户端组件
注意
注意
服务端组件中可以直接导入客户端组件,但客户端组件中并不能导入服务端组件
"use client"用于声明服务端和客户端组件模块之间的边界。当你在文件中定义了一个 "use client",导入的其他模块包括子组件,都会被视为客户端 bundle 的一部分。
组件默认是服务端组件,但当组件导入到客户端组件中会被认为是客户端组件。客户端组件不能导入服务端组件,其实是在告诉你,如果你在服务端组件中使用了诸如 Node API 等,该组件可千万不要导入到客户端组件中。
但你可以将服务端组件以 props 的形式传给客户端组件:
- 举例
"use client";
import { useState } from "react";
export default function ClientComponent({ children }) {
const [count, setCount] = useState(0);
return (
<>
<button onClick={() => setCount(count + 1)}>{count}</button>
{children}
</>
);
}
- 导入服务器端组件
import ClientComponent from "./client-component";
import ServerComponent from "./server-component";
export default function Page() {
return (
<ClientComponent>
<ServerComponent />
</ClientComponent>
);
}
各自渲染各自的不影响