Logo
Published on

Next.js 入门到进阶:(五) 数据获取

Authors
  • avatar
    Name
    xiaobai
    Twitter

在 App Router 中,数据获取的方式发生了根本性的变化。这主要得益于 React Server Components (RSC) 的引入。现在,默认情况下,所有的页面组件都是服务端组件

1. 服务端组件中的数据获取

服务端组件可以直接在组件内部执行异步操作来获取数据,这让代码变得异常简洁和直观。你只需要在函数前加上 async 关键字即可。

让我们来创建一个显示文章列表的页面。

// src/app/posts/page.tsx

type Post = {
  id: number;
  title: string;
  body: string;
};

// 这是一个异步的服务端组件
export default async function PostsPage() {
  // 直接在组件中使用 fetch 获取数据
  const res = await fetch('https://jsonplaceholder.typicode.com/posts?_limit=10');
  const posts: Post[] = await res.json();

  return (
    <main className="p-8">
      <h1 className="text-3xl font-bold mb-6">文章列表</h1>
      <ul className="space-y-4">
        {posts.map((post) => (
          <li key={post.id} className="p-4 border rounded-lg">
            <h2 className="text-xl font-semibold">{post.title}</h2>
            <p className="text-gray-600 mt-2">{post.body}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}

发生了什么?

  1. PostsPage 是一个 async 函数,它会在服务端执行。
  2. fetch 请求在服务端发出,获取数据。
  3. 组件在服务端完成渲染,生成最终的 HTML。
  4. 浏览器收到的是已经包含所有文章内容的完整 HTML 页面。

这种方式既有 SSR 的 SEO 优势,又有简洁的开发体验,并且避免了在客户端和服务器之间传输大量 JSON 数据。

2. 缓存与数据更新

Next.js 对 fetch 函数进行了扩展,使其能够精细地控制缓存策略。

默认缓存策略:静态渲染

默认情况下,Next.js 会尽可能地缓存 fetch 的结果。在上面的例子中,fetch('...') 的结果会在构建时被获取并缓存。之后每次访问 /posts 页面,都会直接返回这个缓存的静态 HTML。这实际上就是静态站点生成 (SSG)

这对于不经常变化的数据(如博客文章、产品列表)来说是绝佳的性能优化。

按需更新:动态渲染

如果你的数据是实时变化的(例如股票价格、天气信息),或者需要根据用户信息展示不同内容,你就需要让页面进行动态渲染 (即传统的 SSR)。

要强制页面进行动态渲染,你有两种主要方式:

方式一:设置 fetch 的缓存选项

fetchcache 选项设置为 'no-store',可以告诉 Next.js 不要缓存这次请求的结果。

// 每次请求都会重新获取数据
const res = await fetch('https://api.example.com/data', { cache: 'no-store' });

方式二:使用动态函数

在页面中使用了某些动态函数,如 headers()cookies(),Next.js 也会自动切换到动态渲染模式。

import { cookies } from 'next/headers';

export default async function ProfilePage() {
  const userCookie = cookies().get('user-token');
  // ... 因为使用了 cookies(),这个页面会动态渲染
}

定期更新:增量静态再生 (ISR)

如果你希望页面大部分时间是静态的,但需要定期更新以获取最新数据(例如新闻网站首页),可以使用 revalidate 选项。

// 数据会被缓存,但每隔 60 秒会重新生成一次
const res = await fetch('https://.../news', { next: { revalidate: 60 } });

这实现了增量静态再生 (Incremental Static Regeneration - ISR)。用户访问到的始终是静态页面,但 Next.js 会在后台悄悄地重新生成页面,确保数据不会太过时。

3. 客户端组件中的数据获取

虽然服务端组件是 App Router 的核心,但有时你仍然需要在客户端获取数据,例如:

  • 基于用户交互的实时搜索。
  • 需要频繁轮询更新的数据。

要在客户端获取数据,你需要将组件声明为客户端组件,通过在文件顶部添加 'use client' 指令。

'use client'; // 声明为客户端组件

import { useState, useEffect } from 'react';

type User = {
  name: string;
};

export default function UserProfile() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/user') // 假设你有一个获取用户信息的 API 路由
      .then((res) => res.json())
      .then((data) => {
        setUser(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <p>加载中...</p>;

  return <h1>欢迎, {user?.name}</h1>;
}

这和传统的 React 数据获取方式(使用 useEffect)完全一样。

总结

Next.js App Router 带来了强大的、以服务端为中心的数据获取模式。

  • 服务端组件是默认选择,可以直接使用 async/await 获取数据。
  • Next.js 扩展了 fetch API,通过 cacherevalidate 选项可以轻松实现 SSG、SSR 和 ISR
  • 对于需要交互和浏览器 API 的场景,可以使用 'use client' 创建客户端组件,并使用传统方式获取数据。

理解并善用这些数据获取策略,是构建高性能 Next.js 应用的关键。下一篇,我们将学习如何创建 API 路由,为你的应用构建后端接口。