Client Components
本教程共 42 篇 · 第 7 篇 · 更新于 2026-07-30 · 约 6 分钟阅读
7. Client Components
本节目标:学会使用
"use client"指令创建客户端组件,掌握交互性和状态管理的实现方式。
什么是 Client Components
Client Components 是传统的 React 组件——在客户端渲染,支持状态、生命周期、事件处理。
在 App Router 中,你需要显式声明一个组件是 Client Component:
'use client'
import { useState } from 'react'
export default function Counter() {
const [count, setCount] = useState(0)
return (
<div>
<p>计数:{count}</p>
<button onClick={() => setCount(count + 1)}>加一</button>
</div>
)
}
文件顶部的 'use client' 指令告诉 Next.js:这个组件及其所有导入的组件都要在客户端运行。
“use client” 的作用
'use client' 定义了一个网络边界(network boundary)。一旦文件标记了它:
- 这个文件导出的所有组件都在客户端运行
- 这个文件导入的所有组件也被打包到客户端
- 不需要给每个子组件都加
"use client"——只需要在边界处加一次
// app/components/search.tsx
'use client'
import { useState } from 'react'
import { Input } from './input' // 也会成为 Client Component
import { Button } from './button' // 也会成为 Client Component
export default function Search() {
const [query, setQuery] = useState('')
// ...
}
什么时候需要 Client Components
| 功能 | 需要 “use client” |
|---|---|
useState / useReducer | 是 |
useEffect / useLayoutEffect | 是 |
onClick / onChange 等事件 | 是 |
useRef | 是 |
| 浏览器 API(window, localStorage) | 是 |
| 自定义 Hook(依赖上述功能) | 是 |
| 纯展示、无交互 | 否 |
状态管理
Client Components 支持所有 React 状态管理方式:
useState
'use client'
import { useState } from 'react'
export default function Toggle() {
const [isOpen, setIsOpen] = useState(false)
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>
{isOpen ? '收起' : '展开'}
</button>
{isOpen && <div>展开的内容</div>}
</div>
)
}
useReducer
适合复杂状态逻辑:
'use client'
import { useReducer } from 'react'
type State = { count: number }
type Action = { type: 'increment' } | { type: 'decrement' }
function reducer(state: State, action: Action) {
switch (action.type) {
case 'increment': return { count: state.count + 1 }
case 'decrement': return { count: state.count - 1 }
}
}
export default function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 })
return (
<div>
<p>{state.count}</p>
<button onClick={() => dispatch({ type: 'increment' })}>+</button>
<button onClick={() => dispatch({ type: 'decrement' })}>-</button>
</div>
)
}
useContext
Server Components 不支持 React Context。如果要在 Server Component 里使用 context,需要创建一个 Client Component 作为 Provider:
// app/theme-provider.tsx
'use client'
import { createContext } from 'react'
export const ThemeContext = createContext('light')
export default function ThemeProvider({
children,
}: {
children: React.ReactNode
}) {
return (
<ThemeContext.Provider value="dark">
{children}
</ThemeContext.Provider>
)
}
// app/layout.tsx
import ThemeProvider from './theme-provider'
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>{children}</ThemeProvider>
</body>
</html>
)
}
生命周期
Client Components 支持 useEffect:
'use client'
import { useEffect, useState } from 'react'
export default function Timer() {
const [seconds, setSeconds] = useState(0)
useEffect(() => {
const id = setInterval(() => {
setSeconds(s => s + 1)
}, 1000)
return () => clearInterval(id) // 清理
}, [])
return <div>{seconds} 秒</div>
}
在 Client 中获取数据
Client Components 有两种数据获取方式:
1. 使用 React 的 use API + 服务端传递 Promise
// Server Component
import Posts from '@/components/posts'
export default function Page() {
const posts = getPosts() // 不 await,传递 Promise
return (
<Suspense fallback={<div>加载中...</div>}>
<Posts posts={posts} />
</Suspense>
)
}
// Client Component
'use client'
import { use } from 'react'
export default function Posts({ posts }: { posts: Promise<any[]> }) {
const allPosts = use(posts) // 解析 Promise
return (
<ul>
{allPosts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
2. 使用社区库(SWR / React Query)
'use client'
import useSWR from 'swr'
const fetcher = (url: string) => fetch(url).then(r => r.json())
export default function BlogPage() {
const { data, error, isLoading } = useSWR(
'https://api.example.com/posts',
fetcher
)
if (isLoading) return <div>加载中...</div>
if (error) return <div>出错了</div>
return (
<ul>
{data.map((post: any) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
混合使用的最佳实践
把交互拆出来
不要整个页面都标记 "use client",只把需要交互的部分拆出来:
// Server Component(默认)
import Search from './search'
import Logo from './logo'
export default function Layout({ children }) {
return (
<nav>
<Logo /> {/* Server Component */}
<Search /> {/* Client Component */}
</nav>
)
}
把 Server Component 作为 props 传递
Server Component 可以作为 children 传给 Client Component:
// Client Component
'use client'
export default function Modal({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false)
return (
<>
<button onClick={() => setIsOpen(true)}>打开</button>
{isOpen && <div className="modal">{children}</div>}
</>
)
}
// Server Component
import Modal from './modal'
import Cart from './cart'
export default function Page() {
return (
<Modal>
<Cart /> {/* Server Component 在 Modal 里渲染 */}
</Modal>
)
}
这样,Cart 在服务端渲染,Modal 处理客户端交互。
第三方组件库
很多第三方库(比如图表库、富文本编辑器)依赖客户端 API。如果它们没有 "use client" 指令,你需要包一层:
// components/carousel.tsx
'use client'
import { Carousel } from 'some-carousel-library'
export default Carousel
现在你可以在 Server Component 里直接使用 <Carousel />。
减少客户端包体积
只在需要交互的地方加 "use client"。把静态内容留在服务端:
// ❌ 不好:整个组件都是客户端
'use client'
export default function Page() {
return (
<div>
<h1>标题</h1>
<p>大量静态内容...</p>
<button onClick={...}>唯一的交互点</button>
</div>
)
}
// ✅ 好:只有按钮是客户端
export default function Page() {
return (
<div>
<h1>标题</h1>
<p>大量静态内容...</p>
<LikeButton />
</div>
)
}
小结
这一章我们掌握了 Client Components:
'use client'指令声明客户端边界- 支持 state、effect、事件、浏览器 API
- 可以接收 Server Component 作为 props
- 数据获取用
useAPI 或 SWR/React Query - 最佳实践:只在需要交互的地方用 Client
下一章,我们来学习数据获取的具体方法。