Next.js第十二章(RSC/服务端组件/客户端组件)

RSC(React Server Components)

RSC(服务器组件)是React19正式引入的一种新的组件类型,它可以在服务器端渲染,也可以在客户端渲染。

像传统的SSR他是在服务器提前把页面渲染好,然后返回给浏览器,然后进行水合,CSR则是在客户端渲染,而RSC则是吸取两方优势,分为服务器组件客户端组件

举个例子:

例如我们有一个官网的页面,上面都是静态内容,但下面留言框是需要交互的。

此时我们就可以拆分成两个组件:

  • 服务器组件: 上面都是静态内容,例如正文,标题,图片等,这类组件之所以适合在服务端执行,核心原因在于服务端渲染HTML+CSS的速度更快,生成的内容对搜索引擎完全可见,且无需客户端额外处理交互逻辑,完美匹配静态内容的需求。
tsx 复制代码
//Next.js 默认服务器组件
export default function HomePage() {
    return (
        <div>
            <h1>Home Page</h1>
        </div>
    )
}
  • 客户端组件: 下面留言框是需要交互的,例如交互功能,如点赞按钮、计数器、表单等。这类组件需要依赖浏览器DOM事件、状态管理(useState)、副作用(useEffect)等客户端能力,必须在客户端完成渲染和水合(即添加事件处理程序的过程)才能实现交互效果
tsx 复制代码
'use client' //声明这是一个客户端组件
export default function HomePage() {
    return (
        <div>
            <h1>Home Page</h1>
        </div>
    )
}

渲染(RSC Payload)

SSR模式是在服务器直接渲染成HTML页面,返回给浏览器的,而RSC他是一种特殊的紧凑的格式

json 复制代码
b2:["$","span",null,{"className":"line","children":["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":"    })"}]}]
b3:["$","span",null,{"className":"line","children":[["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":"  }"}],["$","span",null,{"style":{"color":"var(--shiki-token-punctuation)"},"children":","}],["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":" [])"}]]}]
b4:["$","span",null,{"className":"line","children":" "}]
b5:["$","span",null,{"className":"line","children":[["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":"  "}],["$","span",null,{"style":{"color":"var(--shiki-token-comment)"},"children":"// You can use `isPending` to give users feedback"}]]}]
b6:["$","span",null,{"className":"line","children":[["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":"  "}],["$","span",null,{"style":{"color":"var(--shiki-token-keyword)"},"children":"return"}],["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":" <"}],["$","span",null,{"style":{"color":"var(--shiki-token-string-expression)"},"children":"p"}],["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":">Total Views: {views}</"}],["$","span",null,{"style":{"color":"var(--shiki-token-string-expression)"},"children":"p"}],["$","span",null,{"style":{"color":"var(--shiki-color-text)"},"children":">"}]]}]

那为什么这么做呢?因为我们的组件可以进行嵌套服务器组件>嵌套客户端组件>

黄色节点表示服务器组件,虚线节点表示客户端组件

在这个结构中,Next.js就会标记哪些是客户端组件并且预留好位置,但是不会进行水合。

那么Next.js发现客户端组件也会在服务器生成这个结构,那干脆直接服务器里面把客户端组件进行预渲染(不包含交互),这样我们就能快速看到数据,等他加载完成后再进行水合,所以客户端组件也会在服务器进行一次预渲染

优点

  • 将组件拆分成客户端组件和服务器组件,可以有效的减少bundle体积,因为服务器组件已经在服务器渲染好了,所以没必要打入bundle中,也就是说服务器组件所依赖的包都不会打进去,大大减少了bundle体积。

  • 局部水合,像传统的SSR同构模式, 所有的页面都要在客户端进行水合,而RSC将组件拆分出来,只会把客户端组件进行水合,避免了全量水合带来的性能损耗。

  • 流式加载,我们的HTML页面本来就支持流式加载,所以服务器组件可以边渲染边返回,提高了FCP(首次内容绘制)性能。

服务端组件(Server Components)

在默认情况下, page layout 都是服务端组件,服务端组件可以访问node.js API,包括处理数据库db。

src/app/server/page.tsx

tsx 复制代码
import fs from 'node:fs' //引入fs模块
import mysql, { RowDataPacket } from 'mysql2/promise' //操作数据库 仅供演示 非最佳实践
const pool = mysql.createPool({
    host: 'localhost',
    user: 'root',
    password: '123456',
    database: 'catering',
})

export default async function ServerPage() {
    const [rows] = await pool.query<RowDataPacket[]>('SELECT * FROM goods')
    const data = fs.readFileSync('data.json', 'utf-8')
    const json = JSON.parse(data)
    return (
        <div>
            <h1>Server Page</h1>
            {json.age}///{json.name}///{json.city}
            <h3>mysql</h3>
            {rows.map((item: any) => (
                <div key={item.id}>{item.name}-{item.goodsPrice}</div>
            ))}
        </div>
    )
}

data.json

json 复制代码
{
    "name": "John",
    "age": 30,
    "city": "New York"
}

因为是在服务端渲染的所以日志会出现在控制台,那为什么控制台也会出现,是因为Next.js在本地开发模式方便我们调试进行的输出,后续生产环境就看不到了。

服务端组件的优点

  • 安全性: 我们在服务端组件中访问一些API秘钥,令牌等其他机密,不会暴露给客户端。
  • 体积: 因为服务端组件在服务器渲染,所以不会被打包到客户端,所以体积更小。
  • 全栈:可以在服务端组件访问数据库,文件系统等其他API,实现全栈开发。
  • FCP(首次内容绘制): 因为服务端组件是流式传输,所以边渲染边返回,提高了FCP(首次内容绘制)性能。

服务端组件的缺点

  • 交互性: 因为服务端组件在服务器渲染,所以无法访问浏览器API,所以无法进行交互。
  • hooks: useEffect useState 等hooks在服务端组件中无法使用。

JavaScript: 是由三部分组成的(ECMAScript,DOM,BOM),在服务端组件只能使用ECMAScript部分,无法访问DOMBOM

ECMAScript: 就是我们常用的对象,数组,es6+等这些东西是通用的在客户端和服务端都能用。

如果要使用以下有交互性的功能,我们需要使用客户端组件。

tsx 复制代码
import { useEffect,useState } from 'react'
export default function ServerPage() {
    const [count, setCount] = useState(0)
    useEffect(() => {
        console.log(document,window)
    }, [])
    return (
        <div>
            <h1>Server Page</h1>
            <button onClick={() => setCount(count + 1)}>点击</button>
            <p>{count}</p>
        </div>
    )
}

客户端组件(Client Components)

声明客户端组件需要在文件的顶部编写 'use client' 声明这是客户端组件,但是注意客户端组件会在服务端进行一次预渲染,所以访问document window 等API需要在useEffect中访问。

tsx 复制代码
'use client'
import { useEffect,useState } from 'react'
console.log('client')
export default function ServerPage() {
    const [count, setCount] = useState(0)
    console.log('client X')
    useEffect(() => {
        console.log(document,window)
    }, [])
    return (
        <div>
            <h1>Server Page</h1>
            <button onClick={() => setCount(count + 1)}>点击</button>
            <p>{count}</p>
        </div>
    )
}

所以我们可以看到他把useState的0预渲染了出来这样可以让用户先看到页面。

组件嵌套

服务端组件可以嵌套客户端组件,客户端只能嵌套不能嵌套服务端组件

why:因为客户端会把他所有的模块以及子组件认为是客户端组件,那此时如果服务端组件用了node.js的API,或者其他服务端操作,那就会报错,因为客户端组件无法访问这些API,故此客户端组件不能嵌套服务端组件。

server-only

随着Nodejs的发展,很多API已经可以跟浏览器共用了例如fetch,webSocket,未来Nodejs25支持localStorage等API,所以就会出现这种情况

下面这个函数可以在服务端组件使用,也可以在客户端组件使用,但有时候我们只想让他在服务端使用

ts 复制代码
export default function useTest(type:0 | 1) {
    if (type === 0) {
        return fetch('https://api.github.com')
    } else {
        return new WebSocket('wss://api.github.com')
    }
}
bash 复制代码
npm install server-only
or
yarn add server-only
or
pnpm add server-only

安装完成这个包之后,只需要在文件的顶部编写 import 'server-only' 声明即可,这样他就会在服务端执行,在客户端执行会报错。

tsx 复制代码
import 'server-only'
export default function useTest(type:0 | 1) {
    if (type === 0) {
        return fetch('https://api.github.com')
    } else {
        return new WebSocket('wss://api.github.com')
    }
}

客户端使用报错:

相关推荐
亿元程序员1 小时前
明明直接用就可以了,非要在Creator里面写???
前端
wadesir1 小时前
Nginx负载均衡代理协议详解(从零开始搭建高可用Web服务)
前端·nginx·负载均衡
秋氘渔1 小时前
Vue 3 组合式写法:侦听器 watch 和 watchEffect 的区别及使用技巧
前端·javascript·vue.js·watch·watcheffect
想睡八个小时2 小时前
已包含的文件名 “a.vue“ 仅大小写与文件名 “A.vue“ 不同
前端·vscode
The_era_achievs_hero2 小时前
Echarts
前端·javascript·echarts
涔溪3 小时前
Vite 和 Webpack 这两款主流前端构建工具的核心区别,包括它们的设计理念、工作机制和实际使用体验上的差异。
前端·webpack·vite
0思必得03 小时前
[Web自动化] 开发者工具元素(Elements)面板
运维·前端·自动化·web自动化·开发者工具
遇到困难睡大觉哈哈3 小时前
Harmony os ——ArkTS 语言笔记(五):泛型、空安全与可选链
前端·笔记·安全·harmonyos·鸿蒙
八哥程序员4 小时前
你真的理解了 javascript 中的原型及原型链?
前端·javascript