数据获取(上)
本教程共 42 篇 · 第 8 篇 · 更新于 2026-07-30 · 约 8 分钟阅读
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'
})
NoteNext.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只在当前请求内有效。每个新请求会重新获取数据。
小结
这一章我们学了数据获取的核心方法:
- Server Components 用
async/await直接获取数据 fetch支持缓存配置和自动去重- 并行请求用
Promise.all,串行用<Suspense> - 错误处理:检查
res.ok、error.tsx、notFound() - 流式渲染用
<Suspense>提升感知性能
下一章,我们继续学习数据获取的进阶技巧——params/searchParams 异步化和缓存控制。