Skip to content
第 159 / 250 章前端⏱ 10 分钟阅读

第 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 swr
tsx
'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-query
tsx
'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

动手练习

  1. 写一个 Server Component,获取 GitHub 用户列表
  2. 用 revalidate: 60 实现 ISR
  3. 写一个 Server Action,处理用户创建
  4. 用 SWR 实现客户端数据获取

推荐阅读


下一章:第 160 章:Route Handlers(API)

本站基于 VitePress 构建 · 由 Codebook 团队维护