Skip to content

数据获取

前言

在 Next.js 中如何获取数据呢?

Next.js 优先推荐使用原生的 fetch 方法,因为 Next.js 拓展了原生的 fetch 方法,为其添加了缓存和更新缓存(重新验证)的机制。

这样做的好处在于可以自动复用请求数据,提高性能。坏处在于如果你不熟悉,经常会有一些“莫名奇妙”的状况出现……

让我们来看看具体如何使用吧。

服务端使用 fetch

基本用法

Next.js 拓展了原生的 fetch Web API,可以为服务端的每个请求配置缓存(caching)和重新验证( revalidating)行为。

你可以在服务端组件、路由处理程序、Server Actions 中搭配 async/await 语法使用 fetch

举个例子:

js
// app/page.js
async function getData() {
  const res = await fetch("https://jsonplaceholder.typicode.com/todos");
  if (!res.ok) {
    // 由最近的 error.js 处理
    throw new Error("Failed to fetch data");
  }
  return res.json();
}

export default async function Page() {
  const data = await getData();
  return <main>{JSON.stringify(data)}</main>;
}

默认缓存(15 已经去掉了)

默认情况下,Next.js 会缓存每个请求

js
// fetch 的 cache 选项用于控制该请求的缓存行为
// 默认就是 'force-cache', 平时写的时候可以省略
fetch("https://...", { cache: "force-cache" });
  • 但这些情况不会被自动缓存

注意

  1. 在 Server Action 中使用的时候

  2. 在定义了非 GET 方法的路由处理程序中使用的时候

重新验证

Next.js 中,清除数据缓存并重新获取最新数据的过程就叫做重新验证(Revalidation)。

Next.js 提供了两种方式重新验证:

  • 一种是基于时间的重新验证(Time-based revalidation),即经过一定时间并有新请求产生后重新验证数据,适用于不经常更改且新鲜度不那么重要的数据。

  • 一种是按需重新验证(On-demand revalidation),根据事件手动重新验证数据。按需重新验证又可以使用基于标签(tag-based)和基于路径(path-based)两种方法重新验证数据。适用于需要尽快展示最新数据的场景。

基于时间的重新验证

使用基于时间的重新验证,你需要在使用 fetch 的时候设置 next.revalidate 选项(以秒为单位):

ts
fetch("https://...", { next: { revalidate: 3600 } });
  • 或者通过路由段配置项进行验证,使用这种方法,它会验证该路由段的所有 fetch 请求
ts
// layout.jsx | page.jsx | route.js
export const revalidate = 0; // 0 表示不使用缓存
  • 注意

在一个静态渲染的路由中,如果你有多个请求,每个请求设置了不同的重新验证时间,将会使用最短的时间用于所有的请求。而对于动态渲染的路由,每一个 fetch 请求都将独立重新验证。

按需重新验证(复杂)

  • revalidatePath
ts
import { revalidatePath } from "next/cache";

export async function GET(request) {
  const path = request.nextUrl.searchParams.get("path");

  if (path) {
    revalidatePath(path);
    return Response.json({ revalidated: true, now: Date.now() });
  }

  return Response.json({
    revalidated: false,
    now: Date.now(),
    message: "Missing path to revalidate",
  });
}
  • revalidateTag(了解就好)

Next.js 有一个路由标签系统,可以跨路由实现多个 fetch 请求重新验证。具体这个过程为:

  • 使用 fetch 的时候,设置一个或者多个标签标记请求

  • 调用 revalidateTag 方法重新验证该标签对应的所有请求

举个例子:

ts
// app/page.js
export default async function Page() {
  const res = await fetch("https://...", { next: { tags: ["collection"] } });
  const data = await res.json();
  // ...
}

在这个例子中,为 fetch 请求添加了一个 collection 标签。在 Server Action 中调用 revalidateTag,就可以让所有带 collection 标签的 fetch 请求重新验证。

ts
// app/actions.js
"use server";

import { revalidateTag } from "next/cache";

export default async function action() {
  revalidateTag("collection");
}

让我们真的写个例子。修改 app/page.js 代码如下:

ts
async function getData() {
  const res = await fetch("https://api.thecatapi.com/v1/images/search", {
    next: { tags: ["collection"] },
  });
  if (!res.ok) {
    throw new Error("Failed to fetch data");
  }

  return res.json();
}

export default async function Page() {
  const data = await getData();

  return <img src={data[0].url} width="300" />;
}

退出数据缓存

注意

fetch满足这些条件的时候 会退出数据缓存:

  • fetch 请求添加了 cache: 'no-store' 选项

  • fetch 请求添加了 revalidate: 0 选项

  • fetch 请求在路由处理程序中并使用了 POST 方法

  • 使用 headers 或 cookies 的方法之后使用 fetch 请求

  • 配置了路由段选项 const dynamic = 'force-dynamic'

  • 配置了路由段选项 fetchCache ,默认会跳过缓存

    -fetch 请求使用了 Authorization 或者 Cookie 请求头,并且在组件树中其上方还有一个未缓存的请求

在具体使用的时候,如果你不想缓存某个单独请求:

ts
// layout.js | page.js
fetch("https://...", { cache: "no-store" });

针对多个请求,页面里面可以使用

ts
// layout.js | page.js
export const dynamic = "force-dynamic";

如果使用第三方库的话

页面里面设置 export const dynamic = 'force-dynamic'