首页 / Next.js 16 入门教程 / 数据获取(上)

Next.js 16 入门教程

数据获取(上)

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

Next.jsNext.js 16 入门教程数据获取fetchasync服务端

8. 数据获取(上)

本节目标:学会在 Server Components 中用 async/await 获取数据,掌握并行请求和错误处理。

Server Components 中获取数据

Server Components 支持 async/await,可以直接在组件里获取数据:

// app/blog/page.tsx
export default async function Page() {
  const data = await fetch('https://api.vercel.app/blog')
  const posts = await data.json()

  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

这比 useEffect 获取数据好得多——数据在服务器上就拿到了,直接渲染成 HTML 发给用户。

用 fetch API

Next.js 扩展了原生的 fetch,增加了缓存和去重能力:

// 1. 默认不缓存,每次请求都重新获取
const res = await fetch('https://api.example.com/posts')
// 2. 设置缓存时间(秒)
const res = await fetch('https://api.example.com/posts', {
  next: { revalidate: 3600 } // 缓存 1 小时
})
// 3. 完全不缓存
const res = await fetch('https://api.example.com/posts', {
  cache: 'no-store'
})
Note

Next.js 16 中,fetch 默认不缓存。需要显式设置 use cache 指令或 cacheLife 来控制缓存。

直接查数据库

Server Components 可以直接操作数据库,不会泄露凭证:

import { db } from '@/lib/db'

export default async function Page() {
  const users = await db.select().from(usersTable)

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

并行请求

多个独立的请求应该并行发起,不要串行等待:

串行(慢)

export default async function Page({ params }) {
  const { username } = await params
  const artist = await getArtist(username)    // 等这个完成
  const albums = await getAlbums(username)    // 才开始这个

  return <div>...</div>
}

并行(快)

export default async function Page({
  params,
}: {
  params: Promise<{ username: string }>
}) {
  const { username } = await params

  // 同时发起两个请求
  const artistData = getArtist(username)
  const albumsData = getAlbums(username)

  const [artist, albums] = await Promise.all([artistData, albumsData])

  return <div>...</div>
}
Tip

Promise.all 中一个失败,全部失败。如果需要容忍部分失败,用 Promise.allSettled

串行请求(有时必须)

有些请求依赖前一个请求的结果,只能串行:

export default async function Page({
  params,
}: {
  params: Promise<{ username: string }>
}) {
  const { username } = await params
  const artist = await getArtist(username)

  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<div>加载歌单...</div>}>
        <Playlists artistID={artist.id} />
      </Suspense>
    </>
  )
}

async function Playlists({ artistID }: { artistID: string }) {
  const playlists = await getArtistPlaylists(artistID)
  return (
    <ul>
      {playlists.map(p => <li key={p.id}>{p.name}</li>)}
    </ul>
  )
}

这里用 <Suspense> 让歌单部分流式加载,不阻塞整个页面。

请求去重

Next.js 会自动去重相同的 fetch 请求。同一个请求在组件树里多次调用,只会发一次网络请求:

// app/layout.tsx
export default async function Layout({ children }) {
  const user = await getUser('1')  // 第一次请求
  return (
    <html>
      <body>
        <UserName user={user.name} />
        {children}
      </body>
    </html>
  )
}

// app/dashboard/page.tsx
export default async function Page() {
  const user = await getUser('1')  // 复用上面的请求,不发第二次
  return <h1>欢迎 {user.name}</h1>
}

错误处理

检查响应状态

export default async function Page() {
  const res = await fetch('https://api.example.com/data')

  if (!res.ok) {
    return <div>请求失败,状态码:{res.status}</div>
  }

  const data = await res.json()
  return <div>{data}</div>
}

用 error.tsx 捕获

如果请求抛出异常,会被最近的 error.tsx 捕获:

// app/blog/error.tsx
'use client'

export default function Error({
  error,
  unstable_retry,
}: {
  error: Error
  unstable_retry: () => void
}) {
  return (
    <div>
      <h2>加载失败</h2>
      <p>{error.message}</p>
      <button onClick={() => unstable_retry()}>重试</button>
    </div>
  )
}

用 notFound 处理 404

import { notFound } from 'next/navigation'

export default async function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = await params
  const post = await getPost(slug)

  if (!post) {
    notFound() // 触发 not-found.tsx
  }

  return <div>{post.title}</div>
}

流式渲染

当页面有慢请求时,用 <Suspense> 让部分内容先显示:

import { Suspense } from 'react'

export default function Page() {
  return (
    <div>
      <header>
        <h1>博客首页</h1>
      </header>
      <main>
        <Suspense fallback={<div>加载文章列表...</div>}>
          <BlogPosts />
        </Suspense>
      </main>
    </div>
  )
}

async function BlogPosts() {
  const posts = await fetch('https://api.example.com/posts')
    .then(res => res.json())

  return (
    <ul>
      {posts.map((post: any) => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  )
}

用户立刻看到标题,文章列表加载完后自动替换骨架屏。

共享数据

React.cache 在同一个请求内共享数据:

// app/lib/user.ts
import { cache } from 'react'

export const getUser = cache(async () => {
  const res = await fetch('https://api.example.com/user')
  return res.json()
})
// app/layout.tsx
import { getUser } from './lib/user'

export default function Layout({ children }) {
  const userPromise = getUser() // 不 await

  return (
    <html>
      <body>
        <Suspense fallback={<div>加载中...</div>}>
          <UserMenu userPromise={userPromise} />
        </Suspense>
        {children}
      </body>
    </html>
  )
}
Note

React.cache 只在当前请求内有效。每个新请求会重新获取数据。

小结

这一章我们学了数据获取的核心方法:

  1. Server Components 用 async/await 直接获取数据
  2. fetch 支持缓存配置和自动去重
  3. 并行请求用 Promise.all,串行用 <Suspense>
  4. 错误处理:检查 res.okerror.tsxnotFound()
  5. 流式渲染用 <Suspense> 提升感知性能

下一章,我们继续学习数据获取的进阶技巧——params/searchParams 异步化和缓存控制。