Published on

从 use client指令看Nextjs SSR RSC

背景

use client 是nextjs的一个指令,用于将组件转为客户端组件。从字面来看,可能会造成一些困惑。

比如:客户端组件就只能在客户端执行,服务端不会执行?客户端组件渲染类似spa,只在客户端执行渲染?第一次请求获取的客户端组件是没有内容的?

React SSR 基本原理及Hydrate水合

  • 服务端调用 react-dom/server 包中的 renderToString 将react组件内容渲染内容输出为 html字符串返回给客户端。 此时输出的全是html字符串,无法使用js,无法挂载事件,没有交互。
  • hydrate 将结合服务端返回的 html静态内容和react组件中的js部分,给静态dom添加交互。其实是类似于 客户端react的 render 方法,也是在客户端进行,差异在于 render 会忽略 root dom中现有的 dom,而 hydrate 则会复用root dom中现有的内容,匹配检查。
  • nextjs将上述两部分内容组合起来。最终返回给前端的html中有dom内容,水合部分的js会插入html中通过script引入。
nextjs_ssr_hydrate

Nextjs use client

  • use client 指令的组件会变成客户端组件,与之对应的是 服务器组件RSC(React Server Component) 。客户端组件可以在服务端和客户端两端都渲染,服务器组件只能在服务端渲染,不能在客户端渲染。

  • 服务器组件只会在服务端渲染一次,输出的结果是 html string。可以简单理解为一个nodejs中执行的函数,只不过也是以.jsx结尾,不要和客户端的react组件混淆,服务器组件是一个新的概念。

  • 服务器组件 的好处是减少了js的体积,没有水合的js代码。特别是需要引入较大体积第三方库的组件,现在可以在服务器组件中在服务端生成,从而减少客户端加载的js体积。

  • 服务器组件返回的html中也有会 bundle.js ,里面的代码是一些nextjs的基础代码,如react的runtime,以及用到的客户端组件的代码,不包含服务器组件的代码。会有一个内联的script脚本,用来表示这个服务器组件的JSX描述,用来给客户端的React 复用,直接利用该JSX描述生成fiber,避免重新生成该组件的fibe。

  • 客户端组件也会在服务端执行一次,只会使用该组件初次渲染的数据。比如useState的初始值。不会执行useEffect里面的effect函数,如果直接在组件函数内调用浏览器api会报错,建议将调用浏览器api的代码放入useEffect在客户端执行。

  • 客户端组件无法渲染 RSC,当在客户端组件中import 引入RSC时,会自动将引入的组件转为 客户端组件,从而增加一些水合的性能开销。

  • 客户端边界的组件父子关系并不重要。 use client 指令适用的是 文件/模块级别。即客户端组件中通过 import 引入的组件才会变成客户端组件。如果是在 RSC中使用组合,组合中的父组件是 客户端组件,子组件并不会从 RSC变成 客户端组件。

// /components/ColorProvider.js
'use client';
import { DARK_COLORS, LIGHT_COLORS } from '@/constants.js';
import Footer from './Footer';

function ColorProvider({ children }) {
  const [colorTheme, setColorTheme] = React.useState('light');
  const colorVariables = colorTheme === 'light'
    ? LIGHT_COLORS
    : DARK_COLORS;
  return (
    <body style={colorVariables}>
      {children}
      <Footer />
    </body>
  );
}

// /components/Homepage.js
import Header from './Header';
import MainContent from './MainContent';
import ColorProvider from './ColorProvider';
function Homepage() {
  return (
    <ColorProvider>
      <Header />
      <MainContent />
    </ColorProvider>
  );
}

如上代码,Homepage中import的 Header, MainContent并不会因为是ColorProvider的子组件,就变成了客户端组件。 而Footer组件,由于是在客户端组件中import的,即使本身是RSC,这也会被转为客户端组件。

nextjs_ssr_rsc_use_client