服务端组件与客户端组件

要理解服务端组件 (Server Components) 和客户端组件 (Client Components) 的工作原理,需要先熟悉两个基础的 Web 概念:

  • 应用代码执行的环境:服务端与客户端
  • 隔离服务端与客户端代码的网络边界

服务端与客户端环境

在 Web 应用的上下文中:

示意图左侧为浏览器,右侧为服务器,两者之间由网络边界分隔
  • 客户端指用户设备上的浏览器,它会向服务器发送应用代码请求,并将服务器返回的响应转换为用户可交互的界面
  • 服务端指数据中心存储应用代码的计算机,接收客户端请求后执行计算并返回响应

每个环境都有其特定的能力和限制。例如,将渲染和数据获取移至服务端可以减少发送到客户端的代码量,从而提升应用性能。但如之前所述,要使 UI 具备交互性,仍需在客户端更新 DOM。

因此,为服务端和客户端编写的代码并不完全相同。某些操作(如数据获取或用户状态管理)更适合在特定环境中执行。

网络边界

网络边界是划分不同环境的概念性分界线。

在 React 中,您可以选择在组件树的何处设置网络边界。例如,可以在服务端获取数据并渲染用户帖子(使用服务端组件),然后在客户端为每篇帖子渲染交互式的 LikeButton(使用客户端组件)。

同理,您可以创建在服务端渲染并在页面间共享的 Nav 组件,但若需要显示链接的激活状态,则可以在客户端渲染 Links 列表。

组件树展示了一个布局,包含三个子组件:Nav、Page 和 Footer。Page 组件有两个子组件:Posts 和 LikeButton。Posts 组件在服务端渲染,LikeButton 组件在客户端渲染

在底层,组件会被拆分为两个模块图:**服务端模块图(或树)**包含所有在服务端渲染的服务端组件,**客户端模块图(或树)**包含所有客户端组件。

服务端组件渲染完成后,会以 **React 服务端组件载荷(RSC)**的特殊数据格式发送到客户端。RSC 载荷包含:

  1. 服务端组件的渲染结果
  2. 客户端组件应渲染位置的占位符(或"空洞")及其 JavaScript 文件的引用

React 利用这些信息整合服务端与客户端组件,并在客户端更新 DOM。

让我们看看具体实现方式。

使用客户端组件

如前一章所述,Next.js 默认使用服务端组件——这能提升应用性能,意味着您无需额外操作即可采用它们。

回顾浏览器中的错误提示,Next.js 警告您试图在服务端组件中使用 useState。解决方法是将交互式"点赞"按钮移至客户端组件。

app 文件夹中创建名为 like-button.js 的新文件,导出 LikeButton 组件:

/app/like-button.js
export default function LikeButton() {}

<button> 元素和 handleClick() 函数从 page.js 移至新的 LikeButton 组件:

/app/like-button.js
export default function LikeButton() {
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

接着转移状态 likes 和导入语句:

/app/like-button.js
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

现在,通过在文件顶部添加 React 的 'use client' 指令,将 LikeButton 声明为客户端组件。这会告知 React 在客户端渲染该组件。

/app/like-button.js
'use client';
 
import { useState } from 'react';
 
export default function LikeButton() {
  const [likes, setLikes] = useState(0);
 
  function handleClick() {
    setLikes(likes + 1);
  }
 
  return <button onClick={handleClick}>Like ({likes})</button>;
}

回到 page.js 文件,将 LikeButton 组件导入页面:

/app/page.js
import LikeButton from './like-button';
 
function Header({ title }) {
  return <h1>{title ? title : 'Default title'}</h1>;
}
 
export default function HomePage() {
  const names = ['Ada Lovelace', 'Grace Hopper', 'Margaret Hamilton'];
 
  return (
    <div>
      <Header title="Develop. Preview. Ship." />
      <ul>
        {names.map((name) => (
          <li key={name}>{name}</li>
        ))}
      </ul>
      <LikeButton />
    </div>
  );
}

保存两个文件并在浏览器中查看应用。现在错误已解决,当您修改并保存时,浏览器会自动更新以反映变更。

此功能称为快速刷新 (Fast Refresh),它能即时反馈您的编辑内容,且已由 Next.js 预先配置。

总结

回顾一下,您已了解:

  • 服务端与客户端环境及其适用场景
  • Next.js 默认使用 React 服务端组件以提升性能
  • 如何通过客户端组件为 UI 的局部添加交互性

扩展阅读

关于服务端与客户端组件还有更多内容值得探索:

On this page