首页 / Next.js 16 入门教程 / Client Components

Next.js 16 入门教程

Client Components

本教程共 42 篇 · 第 7 篇 · 更新于 2026-07-30 · 约 6 分钟阅读

Next.jsNext.js 16 入门教程Client Components客户端组件use client交互

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)。一旦文件标记了它:

  1. 这个文件导出的所有组件都在客户端运行
  2. 这个文件导入的所有组件也被打包到客户端
  3. 不需要给每个子组件都加 "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:

  1. 'use client' 指令声明客户端边界
  2. 支持 state、effect、事件、浏览器 API
  3. 可以接收 Server Component 作为 props
  4. 数据获取用 use API 或 SWR/React Query
  5. 最佳实践:只在需要交互的地方用 Client

下一章,我们来学习数据获取的具体方法。