【Next.js 14】网络请求 fetch(使用指南与注意点)

前言

Next.js拓展了原生的fetch,增加了缓存和重新验证机制。在服务端组件中,直接使用fetch请求接口数据。在客户端组件中,可借助第三方库请求接口数据。本文将详细阐述在Next.js中如何使用fetch获取接口数据。

对Next.js14项目搭建还不熟悉的,可先参考我的另一篇文章:给上市公司从0到1搭建Next.js14项目

1. 强缓存

对于基本不更新的网络资源走强缓存策略

在服务端组件中,使用fetch获取数据默认走强缓存策略。即只有当缓存中没有该资源或者手动清除时,才会发起网络请求。

js 复制代码
async function getProjects() {
  const res = await fetch(`https://...`)
  // 相当于 const res = fetch(`https://...`, { cache: 'force-cache' })
  const projects = await res.json()
 
  return projects
}
 
export default async function Index() {
  const projects = await getProjects()
 
  return projects.map((project) => <div>{project.name}</div>)
}

提示:在开发环境中,就算指定强缓存,接口还是会每次去重新请求。在生产环境中,缓存策略会生效,直接返回缓存的值

2. 强缓存有效时间

虽说在强缓存中,只有当缓存中没有该资源或者手动清除时,才会发起网络请求。但强缓存也是有缓存时间的,默认为365天。

对于不经常更新的网络资源可以手动设置缓存有效时间。在fetch请求中使用revalidate指定缓存时间,单位为秒

js 复制代码
//缓存有效期3600s
const revalidatedData = await fetch(`https://...`, { next: { revalidate: 60*60 } })

如果想在缓存时间内进行更新,可修改请求头,例如在请求头中添加版本信息

js 复制代码
async function getData() {
  const data = await fetch('http://127.0.0.1:9000/api/list', {
    next: { revalidate: 36000 },
    headers: {
      version: '20240406',
    },
  }).then((res) => res.json());
  return data;
}

因为 Next.js 在处理请求时,会根据请求头等信息生成唯一的缓存 key,用于缓存响应数据。如果修改请求头中自定义属性version的值,会使 Next.js 重新生成缓存信息

3. 不缓存

对于经常更新的网络资源,不走缓存策略

在fetch中使用{ cache: 'no-store' },指定每次请求都获取最新数据,不读取缓存数据

js 复制代码
const dynamicData = await fetch(`https://...`, { cache: 'no-store' })

4. 不缓存数据共享

对于缓存的请求,即使在不同子组件内多次调用,接口也只会被调用一次,不会影响性能。

对于不缓存的请求,每次发起都会实时返回最新的数据。如果在同一页面内,若两个子组件均需依赖相同的接口数据,一种处理方式是进行状态提升。

在它们共同的父级组件中发起数据请求,并通过props将数据传递至子组件。不过,这种方式可能会有两个问题:当组件层级较为复杂时,通过层层传递props会比较繁琐;若父级组件中包含静态数据,则这些数据会因为需要等待接口数据返回而延迟显示,无法立即呈现给用户。

为了解决这些问题,我们可以利用React的cache API来实现数据共享

1. 新建user/utils.ts

使用cache对接口数据进行缓存

js 复制代码
import { cache } from 'react';

//模拟网络请求
async function getNews(): Promise<{ name: number; id: number }[]> {
  return new Promise((resolve) => {
    const num = Math.random();
    setTimeout(() => {
      resolve([
        {
          name: num,
          id: 1,
        },
        {
          name: 1233,
          id: 2,
        },
      ]);
    }, 1000);
  });
}

export const getItem = cache(async () => {
  const data = await getNews();
  return data;
});

2. 新建user/layout.tsx

在布局组件中请求接口数据

js 复制代码
import { getItem } from './utils';

export default async function Layout({ children }: { children: React.ReactNode }) {
  const data = await getItem();
  return (
    <section>
      {data.map((o) => (
        <div key={o.id}>{o.name}</div>
      ))}
      ------{children}
    </section>
  );
}

3. 新建user/page.tsx

在页面组件中请求接口数据

js 复制代码
import { getItem } from './utils';

export default async function Home() {
  const data = await getItem();
  return (
    <article>
      {data.map((o) => (
        <div key={o.id}>{o.name}</div>
      ))}
    </article>
  );
}

访问路由"/user",页面显示为相同的内容,说明接口只被调用了一次。刷新页面,页面显示的内容同步刷新

5. 客户端组件中获取数据

在服务端组件中,使用fetch获取接口数据。在客户端组件中,可借助第三方库获取接口数据,例如SWR 或者 ahooks 的 useRequest

在客户端组件中获取数据,接口的请求信息将会展示在客户端,如果不想暴露请求头中的token信息,有两种解决办法:

  1. 在服务端组件中获取数据,然后通过props传递给客户端组件

  2. 通过API路由进行接口转发

对API路由不熟悉的,可参考我另一篇文章:【Next.js 14】App Router的使用(下)

6. 配置代理

在服务端组件中,数据请求在服务端,不需要考虑跨域问题。但在客户端组件中,数据请求在浏览器,需要考虑跨域问题。

假设本地开发地址为http://localhost:3000,第三方API地址为https://www.test.com/api/list,在浏览器中请求会报跨域错误。

配置代理,修改next.config.mjs

js 复制代码
const nextConfig = {
  async rewrites() {
    return [
      {
        source: '/api/:path*',
        destination: `https://www.test.com/api/:path*`,
      },
    ];
  },
};

当客户端发送以 /api 开头的请求,Next.js 会自动将其重定向到 https://www.test.com/api

提示:如果项目配置了basePath: '...',rewrites需额外增加basePath: false

js 复制代码
const nextConfig = {
  async rewrites() {
    return [
      {
        //...
        basePath: false,
      },
    ];
  },
};

7. 在动态路由中使用注意点

  1. 动态路由如果不使用generateStaticParams提前指定路由参数,接口设定的缓存策略依旧会生效,但页面的缓存策略为private, no-cache, no-store, max-age=0, must-revalidate

  2. 动态路由中的接口只要有缓存策略为不缓存,使用generateStaticParams生成的动态路由参数,打包后不会生成对应的html文件

  3. 接口数据更新后,使用generateStaticParams打包后的动态路由的html页面内容也会同步更新

8. 封装fetch请求

封装fetch请求,增加请求拦截器和响应拦截器,并可对请求接口统一管理,防止重复书写{ next: { revalidate } }等属性。

因为每个人的封装思路不同,我这里便不直接贴源码了,感兴趣的可直接访问我的源码地址:request.ts

结尾

对Next.js感兴趣的,可先关注我,后续将继续更新相关内容

如果在Next.js中,有更好的fetch封装思路可在评论区留言,本文内容将实时更新

相关推荐
山有木兮木有枝_6 分钟前
JavaScript 设计模式--单例模式
前端·javascript·代码规范
一大树20 分钟前
Vue3 开发必备:20 个实用技巧
前端·vue.js
颜渊呐25 分钟前
uniapp中APPwebview与网页的双向通信
前端·uni-app
10年前端老司机38 分钟前
React 受控组件和非受控组件区别和使用场景
前端·javascript·react.js
夏晚星38 分钟前
vue实现微信聊天emoji表情
前端·javascript
停止重构40 分钟前
【方案】前端UI布局的绝技,响应式布局,多端适配
前端·网页布局·响应式布局·grid布局·网页适配多端
極光未晚41 分钟前
TypeScript在前端项目中的那些事儿:不止于类型的守护者
前端·javascript·typescript
ze_juejin42 分钟前
Vue3 + Vite + Ant Design Vue + Axios + Pinia 脚手架搭建
前端·vue.js
Rrvive43 分钟前
原型与原型链到底是什么?
javascript
lichenyang45343 分钟前
React项目(移动app)
前端