mcpField
S
server-components

server-components

github.com/davepoon开发3.7k152 次安装今天更新
代码仓库

关于 server-components

介绍 Next.js App Router 中 React Server Components 的开发技能,帮助开发者区分服务端与客户端组件,并组织数据获取、交互和流式渲染。

主要功能

  1. 01服务端与客户端组件用法
  2. 02服务端数据传递至客户端
  3. 03children 与插槽组合模式
  4. 04异步及并行数据获取
  5. 05使用 Suspense 流式渲染

适用场景

  1. 01确定 Next.js 应用的组件边界
  2. 02获取后端数据并将敏感信息留在服务端
  3. 03为服务端渲染页面添加交互
  4. 04流式呈现耗时的页面区块

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/davepoon/buildwithclaude --skill server-components

安装后在 Claude Code 中输入/server-components确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

examples/
data-fetching-patterns.md
references/
composition-patterns.md
server-vs-client.md

详细介绍

Next.js 中的 React Server Components

概述

React Server Components(RSC)允许组件在服务器上渲染,从而减少客户端 JavaScript,并支持直接访问数据。在 Next.js App Router 中,所有组件默认都是 Server Components。

Server Components 与 Client Components

Server Components(默认)

Server Components 仅在服务器上运行:

// app/users/page.tsx (Server Component - default)
async function UsersPage() {
  const users = await db.user.findMany() // Direct DB access

  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  )
}

优点:

  • 直接访问数据库/文件系统
  • 将敏感数据保留在服务器上(API 密钥、令牌)
  • 减小客户端代码包体积
  • 自动代码分割
Client Components

添加 'use client' 指令以启用交互功能:

// components/counter.tsx
'use client'

import { useState } from 'react'

export function Counter() {
  const [count, setCount] = useState(0)

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  )
}

在以下情况下使用 Client Components:

  • 使用 useState、useEffect、useReducer
  • 使用事件处理器(onClick、onChange)
  • 使用浏览器 API(window、document)
  • 使用带状态的自定义 Hook

心智模型

可以将组件树看作具有一个“客户端边界”:

Server Component (page.tsx)
├── Server Component (header.tsx)
├── Client Component ('use client') ← boundary
│   ├── Client Component (child)
│   └── Client Component (child)
└── Server Component (footer.tsx)

关键规则:

  1. Server Components 可以导入 Client Components
  2. Client Components 不能导入 Server Components
  3. 可以将 Server Components 作为 children 传递给 Client Components

组合模式

模式 1:服务器数据 → 客户端交互

在 Server Component 中获取数据,然后传递给 Client Component:

// app/products/page.tsx (Server)
import { ProductList } from './product-list'

export default async function ProductsPage() {
  const products = await getProducts()
  return <ProductList products={products} />
}

// app/products/product-list.tsx (Client)
'use client'

export function ProductList({ products }: { products: Product[] }) {
  const [filter, setFilter] = useState('')

  const filtered = products.filter(p =>
    p.name.includes(filter)
  )

  return (
    <>
      <input onChange={e => setFilter(e.target.value)} />
      {filtered.map(p => <ProductCard key={p.id} product={p} />)}
    </>
  )
}
模式 2:将子组件作为 Server Components

通过 children 属性传递 Server Components:

// components/client-wrapper.tsx
'use client'

export function ClientWrapper({ children }: { children: React.ReactNode }) {
  const [isOpen, setIsOpen] = useState(false)

  return (
    <div>
      <button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
      {isOpen && children} {/* Server Component content */}
    </div>
  )
}

// app/page.tsx (Server)
import { ClientWrapper } from '@/components/client-wrapper'
import { ServerContent } from '@/components/server-content'

export default function Page() {
  return (
    <ClientWrapper>
      <ServerContent /> {/* Renders on server! */}
    </ClientWrapper>
  )
}
模式 3:用于复杂布局的插槽

使用多个 children 插槽:

// components/dashboard-shell.tsx
'use client'

interface Props {
  sidebar: React.ReactNode
  main: React.ReactNode
}

export function DashboardShell({ sidebar, main }: Props) {
  const [collapsed, setCollapsed] = useState(false)

  return (
    <div className="flex">
      {!collapsed && <aside>{sidebar}</aside>}
      <main>{main}</main>
    </div>
  )
}

数据获取

异步 Server Components

Server Components 可以是异步的:

// app/posts/page.tsx
export default async function PostsPage() {
  const posts = await fetch('https://api.example.com/posts')
    .then(res => res.json())

  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}
并行获取数据

并行获取多个资源:

export default async function DashboardPage() {
  const [user, posts, analytics] = await Promise.all([
    getUser(),
    getPosts(),
    getAnalytics(),
  ])

  return (
    <Dashboard user={user} posts={posts} analytics={analytics} />
  )
}
使用 Suspense 实现流式传输

流式传输耗时较长的组件:

import { Suspense } from 'react'

export default function Page() {
  return (
    <div>
      <Header /> {/* Renders immediately */}
      <Suspense fallback={<PostsSkeleton />}>
        <SlowPosts /> {/* Streams when ready */}
      </Suspense>
    </div>
  )
}

选择指南

在以下情况下使用 Server Component:

  • 获取数据
  • 访问后端资源
  • 将敏感信息保留在服务器上
  • 减少客户端 JavaScript
  • 组件没有交互功能

在以下情况下使用 Client Component:

  • 使用状态(useState、useReducer)
  • 使用副作用(useEffect)
  • 使用事件监听器
  • 使用浏览器 API
  • 使用带状态的自定义 Hook

常见错误

  1. 不要不必要地添加 'use client'——这会增大代码包体积
  2. 不要尝试在 Client Components 中导入 Server Components
  3. 应当在边界处序列化数据(不包含函数、类或日期)
  4. 应当使用 children 模式进行组合

资源

如需了解详细模式,请参阅:

  • references/server-vs-client.md - 完整对比指南
  • references/composition-patterns.md - 高级组合
  • examples/data-fetching-patterns.md - 数据获取示例

评论

支持 Markdown