• NextJs 数据篇 - 数据获取 | 缓存 | Server Actions


    前言

    之前讲了:

    这篇文章就打算专门讲一下NextJs中的数据获取方式、缓存以及什么是Server Actions

    一. 数据获取 fetch

    NextJs 中,在服务端使用 fetch 函数是常规的数据请求方式,它扩展了原生的API,在此基础上有这么几个功能(针对服务端的请求):

    • caching:缓存
    • revalidating :重新验证

    例如这么一个简单的服务端组件:

    export default async function Page() {
        const res = await fetch('https://jsonplaceholder.typicode.com/posts')
        const data = await res.json()
    
        return (
            <ul>
                {data.map((item: any) => (
                    <li key={item.id}>{item.title}</li>
                ))}
            </ul>
        )
    }
    

    1.1 缓存 caching

    默认情况下,NextJs 会自动缓存服务端中 fetch 请求的返回值。但是在以下情况下不会自动缓存:

    • Server Action 中使用的时候。
    • 在定义了非 GET 方法的路由处理程序中使用。

    即在服务端组件内部或者只有 GET 方法的路由处理程序中使用 fetch 函数,返回结果会自动缓存。

    接下来我们来测试一下缓存,为了更加直观的让大家看到缓存的生效,我们可以在next.config.mjs 文件中增加以下配置,这样在fetch的时候,就会打印相关的信息,包括缓存是否命中

    /** @type {import('next').NextConfig} */
    const nextConfig = {
        logging: {
            fetches: {
                fullUrl: true
            }
        }
    };
    
    export default nextConfig;
    

    ① 服务端组件使用fetch

    例如我有这么一个服务端组件:

    export default async function Page() {
        const res = await fetch('https://jsonplaceholder.typicode.com/posts')
        const data = await res.json()
        return (
            <ul>
                {data.map((item: any) => (
                    <li key={item.id}>{item.title}</li>
                ))}
            </ul>
        )
    }
    

    页面多次访问后:可以发现缓存命中
    在这里插入图片描述

    ② 路由处理器 GET 请求使用fetch

    // app/api/getData/route.ts
    import { NextResponse } from 'next/server'
    
    export async function GET() {
        const res = await fetch('https://jsonplaceholder.typicode.com/posts')
        const data = await res.json()
        return NextResponse.json(data)
    }
    

    然后多次访问 http://localhost:3000/api/getData,结果如下:

    在这里插入图片描述

    1.2 重新验证 revalidating

    重新验证的定义:清除缓存数据,然后重新获取最新的数据。 而NextJs 中提供了两种方式完成重新验证:

    • 基于时间的重新验证:根据指定的时间,自动进行重新验证。
    • 按需重新验证:根据事件手动重新验证数据。

    ① 基于时间的重新验证

    这种验证方式,只需要在fetch的时候,增加参数revalidate即可,如下代表这个请求的缓存时长为10秒钟

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

    或者在路由段配置项中进行配置,可以再页面上或者路由处理程序中增加以下属性的导出:

    // layout.jsx | page.jsx | route.js
    export const revalidate = 10
    

    ② 按需重新验证

    我们可以在路由处理程序中或者 Server Actions 中通过两种方式来实现按需重新验证:

    • 路径 revalidatePath
    • 缓存标签 revalidateTag
    revalidatePath

    我们写一个简单的页面,这个页面每次加载的时候都会随机加载一个图片,但是由于fetch被缓存了,加载多次还是同一个图片。

    async function getData() {
      // 接口每次调用都会返回一个随机的猫猫图片数据
      const res = await fetch('https://api.thecatapi.com/v1/images/search')
      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请求做到刷新呢?例如我创建这么一个路由处理程序:

    // app/api/revalidatePathTest/route.ts
    import { revalidatePath } from 'next/cache'
    import { NextRequest } from 'next/server'
    
    export async function GET(request: NextRequest) {
        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',
        })
    }
    

    这段代码啥意思呢?

    • 如果我请求的地址没有参数 refreshPath,那就是个普通的接口。
    • 如果我请求的地址带上参数 refreshPath,就会通过revalidatePath 函数,重新验证对应路由或者接口。

    倘若我访问:http://localhost:3000/api/revalidatePathTest?refreshPath=/,之后再访问:http://localhost:3000/,可见图片发生了刷新:
    在这里插入图片描述
    说明成功让路由 / 进行了重新验证(缓存刷新)

    revalidateTag

    除此之外,NextJs中有一个路由标签系统,即revalidateTag,它的实现逻辑如下:

    使用 fetch 的时候,设置一个或者多个标签标记请求
    调用 revalidateTag 方法重新验证该标签对应的所有请求

    例如我们修改app/page.tsx文件:

    async function getData() {
      // 接口每次调用都会返回一个随机的猫猫图片数据
      const res = await fetch('https://api.thecatapi.com/v1/images/search', { next: { tags: ['refresh'] } })
      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" />
    }
    

    修改revalidatePathTest

    // app/api/revalidatePathTest/route.ts
    import { revalidatePath } from 'next/cache'
    import { NextRequest } from 'next/server'
    
    export async function GET(request: NextRequest) {
      const tag = request.nextUrl.searchParams.get('tag')
      revalidateTag(tag)
      return Response.json({ revalidated: true, now: Date.now() })
    }
    

    操作如下:
    在这里插入图片描述
    我们可以发现:

    1. 当我们多次刷新 http://localhost:3000/ ,图片并没有改变,因为缓存的作用。
    2. 当我们访问:http://localhost:3000/api/revalidatePathTest?tag=refresh,我们带上了指定的tag。值为refresh
    3. 此时再次访问首页,发现图片修改。
    4. 而我们的图片在fetch的时候,await fetch('https://api.thecatapi.com/v1/images/search', { next: { tags: ['refresh'] } }),指定了tagrefresh
    5. 因此可以联动做到重新验证。

    1.3 缓存的退出方式

    上面说的都是重新验证,说白了就是缓存的一种刷新机制,那么我们是否有办法主动退出缓存呢?

    当使用 fetch 的时候,若满足以下情形,可以做到默认退出缓存机制:

    • fetch 请求添加了 cache: 'no-store' 或者 revalidate: 0 属性。例如
    fetch('', { cache: 'no-store' })
    
    • fetch 请求在路由处理程序中并使用了其他方法,例如POST
    • 函数体内使用到了 headerscookies 等方法。
    export async function GET(request) {
      const token = request.cookies.get('token')
      return Response.json({ data: new Date().toLocaleTimeString() })
    }
    
    • 配置了路由段选项 const dynamic = 'force-dynamic'
    export const dynamic = 'force-dynamic'
    
    • 配置了路由段选项 fetchCache ,默认会跳过缓存

    二. Server Actions

    Server Actions 是指在服务端执行的异步函数但是可以在服务端和客户端组件中使用。 我们什么情况下可以用到这个呢?

    • PageRouter 情况下,若需要前后端进行交互,则需要先定义一个接口。
    • AppRouter 情况下,这种操作则可以简化为 Server Actions 。我们可以定义一个 Server Actions ,然后直接在客户端使用获取数据。

    它的基本定义方式就是通过 'use server' 声明,一般分为两种:

    • 模块级别:在文件的顶部声明,那么该文件下声明的所有导出函数都是Server Actions
    • 函数级别:在函数内部的顶端添加声明,那么只有该函数是Server Actions

    例如:

    // 函数级别 
    async function getData() {
       'use server'
       // ...
    }
    
    // 模块级别 app/serverActions/action.ts
    'use server'
    export async function getData() {
        // ...
    }
    export async function create() {
        // ...
    }
    

    2.1 PageRouter下 API 简单案例

    我们定义一个接口:
    在这里插入图片描述

    import { NextRequest, NextResponse } from 'next/server'
    
    export async function GET(request: NextRequest) {
        const res = await fetch('https://jsonplaceholder.typicode.com/posts')
        const data = await res.json()
        return NextResponse.json(data)
    }
    

    然后定义一个页面,通过API的方式从后端取数据。

    'use client'
    
    import { useEffect, useState } from "react"
    
    export default function Page() {
        const [data, setData] = useState<any>(null)
        async function getList() {
            const data = await (await fetch('/api/getData')).json();
            setData(data);
        }
        useEffect(() => {
            getList();
        }, [])
        return (
            <ul>
                {data?.map((item: any) => (
                    <li key={item.id}>{item.title}</li>
                ))}
            </ul>
        )
    }
    

    2.2 AppRouter 下 Server Actions 简单案例

    我们来看下使用Server Actions的简单案例,一般我们定义一个actions文件夹:
    在这里插入图片描述
    里面的函数如下:

    'use server'
    export async function getData() {
        const res = await fetch('https://jsonplaceholder.typicode.com/posts')
        const data = await res.json()
        return data;
    }
    

    然后在组件中使用:服务端组件和客户端组件都可以使用

    import { getData } from '../actions/actions'
    
    export default async function Page() {
        const data = await getData();
        return (
            <ul>
                {data.map((item: any) => (
                    <li key={item.id}>{item.title}</li>
                ))}
            </ul>
        )
    }
    

    可以看到Server Actions代码更加简洁,无需手动创建一个接口。并且这个函数可以在代码的任何一个地方使用。

  • 相关阅读:
    WebRTC与CSS滤镜(CSS filter)
    回忆旅途的过往
    “如何应用数据分析提升软件开发流程效率?”
    Leetcode 340. 至多包含 K 个不同字符的最长子串(滑动窗口)
    DDR电源硬件设计要点
    nodeJs--http模块
    数据链路层的七七八八
    【Leetcode】446. Arithmetic Slices II - Subsequence
    vue3 Vuex
    记一次栈溢出异常问题的排查
  • 原文地址:https://blog.csdn.net/Zong_0915/article/details/139375956