第 22 章:Next.js 数据获取
学习目标
- 掌握 Server Component 中 fetch 的 4 种缓存策略
- 学会 Route Handlers(API Routes)
- 理解 Server Actions(React 19/Next 14+)
- 避开数据获取的 4 个常见坑
一、为什么在 Server Component 取数?
tsx
// app/users/page.tsx(Server)
async function getUsers() {
const r = await fetch('https://api.example.com/users', {
next: { revalidate: 60 }, // ISR:60 秒缓存
});
return r.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<ul>
{users.map((u: User) => <li key={u.id}>{u.name}</li>)}
</ul>
);
}优势:
- 直接在服务器 await,无 loading 抖动
- 自动数据去重
- SEO 友好
- 不暴露 API key
二、4 种缓存策略
tsx
// 1. 静态(默认,构建时)
fetch(url, { cache: 'force-cache' });
// 2. 不缓存(每次请求重新拿)
fetch(url, { cache: 'no-store' });
// 3. ISR(指定秒数后过期)
fetch(url, { next: { revalidate: 60 } });
// 4. 标签失效(精确控制)
fetch(url, { next: { tags: ['users'] } });revalidateTag 主动失效
tsx
// app/api/users/route.ts
import { revalidateTag } from 'next/cache';
export async function POST() {
// 写操作后失效
revalidateTag('users');
}三、Suspense 流式渲染
tsx
// app/page.tsx
import { Suspense } from 'react';
async function SlowData() {
await new Promise(r => setTimeout(r, 2000));
const r = await fetch('https://slow-api/data');
return <div>{await r.text()}</div>;
}
export default function Page() {
return (
<>
<h1>立即显示</h1>
<Suspense fallback={<Skeleton />}>
<SlowData /> {/* 慢的部分流式加载 */}
</Suspense>
</>
);
}四、Route Handlers(API 路由)
tsx
// app/api/users/route.ts
import { NextRequest, NextResponse } from 'next/server';
export async function GET(req: NextRequest) {
const users = await db.users.findAll();
return NextResponse.json(users);
}
export async function POST(req: NextRequest) {
const body = await req.json();
const user = await db.users.create(body);
return NextResponse.json(user, { status: 201 });
}支持的 HTTP 方法:GET / POST / PUT / PATCH / DELETE / OPTIONS / HEAD。
⚠️ 坑 1:
route.ts文件名固定,不能叫api.ts;同目录下有page.tsx会冲突。
五、动态路由 API
tsx
// app/api/users/[id]/route.ts
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
const user = await db.users.findById(params.id);
if (!user) return NextResponse.json({ error: 'Not found' }, { status: 404 });
return NextResponse.json(user);
}六、Server Actions(RSC 表单)
Next.js 14 引入,直接在服务端处理表单提交,不需要写 API:
tsx
// app/actions.ts
'use server';
import { revalidatePath } from 'next/cache';
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
await db.users.create({ name });
revalidatePath('/users'); // ⚠️ 失效缓存
}
// app/users/page.tsx
import { createUser } from '../actions';
export default function Page() {
return (
<form action={createUser}>
<input name="name" />
<button type="submit">提交</button>
</form>
);
}优势:
- 无需写 API Route
- 自动 CSRF 保护
- 与 Server Component 无缝衔接
⚠️ 坑 2:
'use server'必须放在文件顶部或函数顶部。
七、Server Action + useFormState
tsx
'use server';
export async function login(prev: State, formData: FormData) {
const email = formData.get('email');
if (!email) return { error: '邮箱必填' };
// ...
return { error: null };
}
// 'use client';
import { useFormState } from 'react-dom';
const initial = { error: null };
export function Form() {
const [state, action] = useFormState(login, initial);
return (
<form action={action}>
<input name="email" />
{state.error && <span>{state.error}</span>}
<button>登录</button>
</form>
);
}八、客户端数据获取
服务端组件 + SWR / TanStack Query:
tsx
'use client';
import useSWR from 'swr';
function Users() {
const { data, error } = useSWR('/api/users', fetcher);
if (error) return <div>失败</div>;
if (!data) return <div>加载</div>;
return <ul>{data.map(...)}</ul>;
}九、Streaming 与 Partial Prerendering
Next.js 14 引入 PPR(实验),静态壳 + 动态流:
tsx
// next.config.js
module.exports = {
experimental: { ppr: true },
};
// 页面
export const experimental_ppr = true;
export default async function Page() {
return (
<>
<StaticHeader />
<Suspense fallback={<Loading />}>
<DynamicContent />
</Suspense>
</>
);
}⚠️ 坑 3:PPR 还在实验,生产前查最新文档。
十、本章小结
| 要点 | 关键 |
|---|---|
| Server fetch | 默认静态,可设 revalidate/no-store/tags |
| 缓存失效 | revalidateTag / revalidatePath |
| 流式渲染 | <Suspense> 包异步组件 |
| Route Handler | app/api/.../route.ts |
| Server Actions | 'use server',直接处理表单 |
| 客户端数据 | SWR / TanStack Query |
| PPR | 实验特性,静态壳 + 动态流 |
动手练习
- 静态数据:
/users服务端组件 fetch 用户列表 - 动态 API:
/api/usersGET/POST,支持增删 - Server Action:做一个留言表单,提交后 revalidate
- 找坑:以下代码为什么 build 报错?tsx
// app/page.tsx 'use client'; export default async function Page() { ... } // async + 'use client' 冲突
下一章:第 23 章:Tailwind CSS →