🍂落页
登 录

Next.js 笔记

  • 安装及项目结构
  • 路由
  • 获取数据
  • 渲染
  • 缓存
  • 优化
🍂落页
TALAXY
获取数据
🍂落页
TALAXY

获取数据

获取数据
  • 数据获取、缓存、重新校验
  • 修改数据
  • 最佳实践

数据获取、缓存、重新校验

服务端使用 fetch 获取数据

Next.js 扩展了原生的 fetch Web API ,允许对缓存及重新校验行为进行配置;React 也扩展了 fetch 方法,当渲染组件树时 React 会自动记忆其中的 fetch 请求。

但是 Route Handler 不会记忆 fetch 请求,因为它不归于 React 。

可以在服务端组件、Route Handler 、Server Action 中使用 fetch :

async function getData() {
    const res = await fetch('https://api.example.com/...');
    if (!res.ok) throw new Error('Failed to fetch data');
    return res.json();
}

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

缓存数据

默认情况下,Next.js 会自动把每次 fetch 请求结果存在服务端:

// 'force-cache' 是默认的,可以省略
fetch('https://...', { cache: 'force-cache' })

POST 请求也会被缓存,但在 Route Handler 中不会被缓存。

重新校验数据

有两种重新校验缓存数据的方式:基于时间的重新校验 和 按需进行重新校验 。

对于一些变更不频繁或者新鲜度并不是很重要的数据,可以设置一个合理的时间间隔来自动进行重新校验:

fetch('https://...', { next: { revalidate: 3600 } })

或者导出一个路由片段配置:

export const revalidate = 3600

如果在静态渲染路由上有多个 fetch 请求,且每个请求都有不同的重新校验频率,会取其中的最小时间。对于动态渲染路由,每个 fetch 请求的重新校验都是相互独立的。

错误处理

如果在重新校验的过程中抛出了错误,则会使用缓存临时替代。在下一次请求中,Next.js 仍会尝试重新校验数据。

不使用缓存

以下方式均可让 fetch 不做数据缓存处理:

  • 在 fetch 中加上 cache: 'no-store' ;
  • 在 fetch 中加上 revalidate: 0 ;
  • 处于 POST 方法的 Router Handler 内的 fetch ;
  • 在使用了 headers 或 cookies 之后的 fetch ;
  • 使用了 const dynamic = 'force-dynamic' 的路由片段配置;
  • 通过路由片段配置 fetchCache 设置为默认跳过缓存;
  • 使用了 Authorization 或 Cookie 标头的请求,且在组件树上有一个未缓存的请求位于其之上。

通常来讲,如果是单独的 fetch 请求,可以直接用 cache 选项来控制缓存;如果在路由片段中有多个 fetch 请求,可以通过路由片段配置来设置所有的请求的默认缓存行为(不过更建议对每一个 fetch 请求都单独地进行缓存行为的设置)。

用第三方库请求数据

TODO

客户端调用 Route Handler

在客户端可以调用 Route Handler 。这对一些场景比较有用,比如当你不想在响应中暴露一些敏感信息(比如 API token)。

修改数据

TODO

最佳实践

服务端获取数据

通常更推荐在服务端获取数据(如果可以的话),可以:

  • 直接访问后端资源(比如数据库);
  • 防止一些敏感信息暴露给客户端,比如 access token 或者 API key 等;
  • 使数据请求和渲染处于同一环境中,减少客户端和服务端的沟通成本;
  • 在客户端上使用单次往返执行多个数据获取,而不是直接发出多个独立的请求。
  • 减少“客户端-服务端”的瀑布流;
  • 服务端可能更接近你的数据资源,可以缩短请求时间。

可通过服务端组件、Route Handler 、Server Action 来在服务端获取数据。

在需要的地方获取数据

TODO

如果在组件树上有多个组件需要使用同一数据,可以直接用 fetch 或 React cache 来获取数据,两者都会自动做 记忆化处理 ,不会导致重复的请求。

流式渲染

对于有多个请求的页面,请求这些接口的模式有两种:依次请求和同时请求。

依次请求

该模式的场景通常为一个请求为另一个请求的依赖。在这种场景,对于有依赖的接口可以用 Suspense 进行等待,确保用户能先触达先前已请求数据相关的页面内容:

async function Playlists({ artistID }) {
    const playlists = await getArtistPlaylists(artistID);
    
    return (
        <ul>
            {playlists.map((playlist) => (
                <li key={playlist.id}>{playlist.name}</li>
            ))}
        </ul>
    );
}
 
export default async function Page({ params: { username } }) {
    const artist = await getArtist(username);
    
    return (
        <>
            <h1>{artist.name}</h1>
            <Suspense fallback={<div>Loading...</div>}>
                <Playlists artistID={artist.id} />
            </Suspense>
        </>
    );
}

同时请求

一个组件中如果有不相依赖的接口,通常会同时请求这些接口:

import Albums from './albums'
 
async function getArtist(username) {
    const res = await fetch(`https://api.example.com/artist/${username}`)
    return res.json()
}
 
async function getArtistAlbums(username) {
    const res = await fetch(`https://api.example.com/artist/${username}/albums`)
    return res.json()
}
 
export default async function Page({ params: { username } }) {
    const artistData = getArtist(username)
    const albumsData = getArtistAlbums(username)

    const [artist, albums] = await Promise.all([artistData, albumsData])
    
    return (
        <>
            <h1>{artist.name}</h1>
            <Albums list={albums}></Albums>
        </>
    )
}

但同时请求可能会让用户等待的时间过长。如果可以的话可以根据接口拆分页面,并分别用 Suspense 来各自等待数据请求。

预加载数据

TODO

避免将敏感信息暴露给用户

TODO

TALAXY 落于 2023年11月29日 。