第 159 章:Server Components 数据获取
学习目标
- 掌握 Server Components 数据获取
- 学会 fetch 缓存与重新验证
- 理解 Server Actions
- 组合加载与错误状态
一、Server Components 数据获取
App Router 默认所有组件都是 Server Components,可以直接 await 异步数据。
1.1 直接 await
tsx
// app/users/page.tsx - Server Component
async function getUsers(): Promise<User[]> {
const res = await fetch('https://api.example.com/users', {
cache: 'no-store', // 不缓存
});
if (!res.ok) throw new Error('获取失败');
return res.json();
}
export default async function UsersPage() {
const users = await getUsers();
return (
<ul>
{users.map(u => <li key={u.id}>{u.name}</li>)}
</ul>
);
}1.2 优势
二、fetch 缓存策略
2.1 缓存选项
tsx
// 1. 默认:静态缓存(force-cache)
const res = await fetch('/api/data');
// 2. 不缓存(动态)
const res = await fetch('/api/data', { cache: 'no-store' });
// 3. 增量静态再生成(ISR)
const res = await fetch('/api/data', {
next: { revalidate: 60 }, // 60 秒后重新验证
});
// 4. 标签失效
const res = await fetch('/api/data', {
next: { tags: ['users'] },
});2.2 revalidatePath / revalidateTag
tsx
import { revalidatePath, revalidateTag } from 'next/cache';
// 失效特定路径
revalidatePath('/users');
// 失效标签
revalidateTag('users');三、加载与错误
3.1 loading.tsx
tsx
// app/users/loading.tsx
export default function Loading() {
return <div className="animate-pulse">加载中...</div>;
}3.2 error.tsx
tsx
'use client';
// app/users/error.tsx
export default function Error({ error, reset }: {
error: Error;
reset: () => void;
}) {
return (
<div className="p-4">
<p className="text-red-500">{error.message}</p>
<button onClick={reset} className="rounded bg-blue-500 px-4 py-2 text-white">
重试
</button>
</div>
);
}四、并行数据获取
4.1 顺序(慢)
tsx
async function Page() {
const user = await getUser(); // 100ms
const posts = await getPosts(user.id); // 200ms
// 总计 300ms
}4.2 并行(快)
tsx
async function Page() {
// ✅ 同时发起
const [user, posts] = await Promise.all([
getUser(),
getPosts(),
]);
// 总计 200ms
}4.3 Preload 模式
tsx
import { cache } from 'react';
import { getUser } from './api';
const getUserCached = cache(getUser);
async function Header() {
const user = await getUserCached();
}
async function Content() {
const user = await getUserCached(); // 复用,只请求一次
}五、Server Actions(Next.js 14+)
服务端函数,可在客户端调用,自动处理表单提交和数据变更。
5.1 基础
tsx
// app/actions.ts
'use server';
export async function createUser(formData: FormData) {
const name = formData.get('name') as string;
await db.user.create({ data: { name } });
revalidatePath('/users');
}5.2 在 form 中使用
tsx
// app/users/new/page.tsx
import { createUser } from '@/app/actions';
export default function NewUser() {
return (
<form action={createUser}>
<input name="name" />
<button type="submit">创建</button>
</form>
);
}5.3 配合 useFormState
tsx
'use client';
import { useFormState, useFormStatus } from 'react-dom';
import { createUser } from './actions';
const initialState = { message: '' };
function Form() {
const [state, formAction] = useFormState(createUser, initialState);
return (
<form action={formAction}>
<input name="name" />
<Submit />
{state.message && <p>{state.message}</p>}
</form>
);
}
function Submit() {
const { pending } = useFormStatus();
return <button disabled={pending}>{pending ? '提交中' : '提交'}</button>;
}六、客户端数据获取(补充)
6.1 SWR
bash
pnpm add swrtsx
'use client';
import useSWR from 'swr';
const fetcher = (url: string) => fetch(url).then(r => r.json());
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher);
if (isLoading) return <div>加载中</div>;
if (error) return <div>错误</div>;
return <div>欢迎,{data.name}</div>;
}6.2 React Query
bash
pnpm add @tanstack/react-querytsx
'use client';
import { useQuery } from '@tanstack/react-query';
function Users() {
const { data, isLoading } = useQuery({
queryKey: ['users'],
queryFn: () => fetch('/api/users').then(r => r.json()),
});
if (isLoading) return <div>加载中</div>;
return <ul>{data?.map((u: User) => <li key={u.id}>{u.name}</li>)}</ul>;
}七、数据获取方式对比
| 方式 | 位置 | 适合 |
|---|---|---|
| Server Component fetch | 服务端 | 静态/SEO 内容 |
| Server Actions | 服务端 | 表单提交/数据变更 |
| SWR / React Query | 客户端 | 频繁更新、用户交互 |
| useEffect | 客户端 | 不推荐(新项目) |
八、本章小结
| 概念 | 关键 |
|---|---|
| Server Component | 默认,直接 await |
| fetch 缓存 | cache / revalidate / tags |
| Server Actions | 'use server',表单提交 |
| 并行 | Promise.all |
| 客户端补充 | SWR / React Query |
动手练习
- 写一个 Server Component,获取 GitHub 用户列表
- 用 revalidate: 60 实现 ISR
- 写一个 Server Action,处理用户创建
- 用 SWR 实现客户端数据获取