前言
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信息,有两种解决办法:
-
在服务端组件中获取数据,然后通过props传递给客户端组件
-
通过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. 在动态路由中使用注意点
-
动态路由如果不使用generateStaticParams提前指定路由参数,接口设定的缓存策略依旧会生效,但页面的缓存策略为private, no-cache, no-store, max-age=0, must-revalidate
-
动态路由中的接口只要有缓存策略为不缓存,使用generateStaticParams生成的动态路由参数,打包后不会生成对应的html文件
-
接口数据更新后,使用generateStaticParams打包后的动态路由的html页面内容也会同步更新
8. 封装fetch请求
封装fetch请求,增加请求拦截器和响应拦截器,并可对请求接口统一管理,防止重复书写{ next: { revalidate } }等属性。
因为每个人的封装思路不同,我这里便不直接贴源码了,感兴趣的可直接访问我的源码地址:request.ts
结尾
对Next.js感兴趣的,可先关注我,后续将继续更新相关内容
如果在Next.js中,有更好的fetch封装思路可在评论区留言,本文内容将实时更新