自己建網(wǎng)站做淘寶客靠譜嗎廣告軟文小故事200字
NextJs 數(shù)據(jù)篇 - 數(shù)據(jù)獲取 | 緩存 | Server Actions
- 前言
- 一. 數(shù)據(jù)獲取 fetch
- 1.1 緩存 caching
- ① 服務(wù)端組件使用fetch
- ② 路由處理器 GET 請求使用fetch
- 1.2 重新驗(yàn)證 revalidating
- ① 基于時間的重新驗(yàn)證
- ② 按需重新驗(yàn)證
- revalidatePath
- revalidateTag
- 1.3 緩存的退出方式
- 二. Server Actions
- 2.1 PageRouter下 API 簡單案例
- 2.2 AppRouter 下 Server Actions 簡單案例
前言
之前講了:
- NextJs 初級篇 - 安裝 | 路由 | 中間件
- NextJs 渲染篇 - 什么是CSR、SSR、SSG、ISR 和服務(wù)端/客戶端組件
這篇文章就打算專門講一下NextJs
中的數(shù)據(jù)獲取方式、緩存以及什么是Server Actions
一. 數(shù)據(jù)獲取 fetch
NextJs
中,在服務(wù)端使用 fetch
函數(shù)是常規(guī)的數(shù)據(jù)請求方式,它擴(kuò)展了原生的API
,在此基礎(chǔ)上有這么幾個功能(針對服務(wù)端的請求):
caching
:緩存revalidating
:重新驗(yàn)證
例如這么一個簡單的服務(wù)端組件:
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
默認(rèn)情況下,NextJs
會自動緩存服務(wù)端中 fetch
請求的返回值。但是在以下情況下不會自動緩存:
- 在
Server Action
中使用的時候。 - 在定義了非
GET
方法的路由處理程序中使用。
即在服務(wù)端組件內(nèi)部或者只有 GET
方法的路由處理程序中使用 fetch
函數(shù),返回結(jié)果會自動緩存。
接下來我們來測試一下緩存,為了更加直觀的讓大家看到緩存的生效,我們可以在next.config.mjs
文件中增加以下配置,這樣在fetch
的時候,就會打印相關(guān)的信息,包括緩存是否命中
/** @type {import('next').NextConfig} */
const nextConfig = {logging: {fetches: {fullUrl: true}}
};export default nextConfig;
① 服務(wù)端組件使用fetch
例如我有這么一個服務(wù)端組件:
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>)
}
頁面多次訪問后:可以發(fā)現(xiàn)緩存命中
② 路由處理器 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
,結(jié)果如下:
1.2 重新驗(yàn)證 revalidating
重新驗(yàn)證的定義:清除緩存數(shù)據(jù),然后重新獲取最新的數(shù)據(jù)。 而NextJs 中提供了兩種方式完成重新驗(yàn)證:
- 基于時間的重新驗(yàn)證:根據(jù)指定的時間,自動進(jìn)行重新驗(yàn)證。
- 按需重新驗(yàn)證:根據(jù)事件手動重新驗(yàn)證數(shù)據(jù)。
① 基于時間的重新驗(yàn)證
這種驗(yàn)證方式,只需要在fetch
的時候,增加參數(shù)revalidate
即可,如下代表這個請求的緩存時長為10秒鐘
fetch('https://...', { next: { revalidate: 10 } })
或者在路由段配置項(xiàng)中進(jìn)行配置,可以再頁面上或者路由處理程序中增加以下屬性的導(dǎo)出:
// layout.jsx | page.jsx | route.js
export const revalidate = 10
② 按需重新驗(yàn)證
我們可以在路由處理程序中或者 Server Actions
中通過兩種方式來實(shí)現(xiàn)按需重新驗(yàn)證:
- 路徑
revalidatePath
- 緩存標(biāo)簽
revalidateTag
revalidatePath
我們寫一個簡單的頁面,這個頁面每次加載的時候都會隨機(jī)加載一個圖片,但是由于fetch被緩存了,加載多次還是同一個圖片。
async function getData() {// 接口每次調(diào)用都會返回一個隨機(jī)的貓貓圖片數(shù)據(jù)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
請求做到刷新呢?例如我創(chuàng)建這么一個路由處理程序:
// 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',})
}
這段代碼啥意思呢?
- 如果我請求的地址沒有參數(shù)
refreshPath
,那就是個普通的接口。 - 如果我請求的地址帶上參數(shù)
refreshPath
,就會通過revalidatePath
函數(shù),重新驗(yàn)證對應(yīng)路由或者接口。
倘若我訪問:http://localhost:3000/api/revalidatePathTest?refreshPath=/
,之后再訪問:http://localhost:3000/
,可見圖片發(fā)生了刷新:
說明成功讓路由 /
進(jìn)行了重新驗(yàn)證(緩存刷新)
revalidateTag
除此之外,NextJs
中有一個路由標(biāo)簽系統(tǒng),即revalidateTag
,它的實(shí)現(xiàn)邏輯如下:
使用 fetch
的時候,設(shè)置一個或者多個標(biāo)簽標(biāo)記請求
調(diào)用 revalidateTag
方法重新驗(yàn)證該標(biāo)簽對應(yīng)的所有請求
例如我們修改app/page.tsx
文件:
async function getData() {// 接口每次調(diào)用都會返回一個隨機(jī)的貓貓圖片數(shù)據(jù)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() })
}
操作如下:
我們可以發(fā)現(xiàn):
- 當(dāng)我們多次刷新
http://localhost:3000/
,圖片并沒有改變,因?yàn)榫彺娴淖饔谩?/li> - 當(dāng)我們訪問:
http://localhost:3000/api/revalidatePathTest?tag=refresh
,我們帶上了指定的tag
。值為refresh
。 - 此時再次訪問首頁,發(fā)現(xiàn)圖片修改。
- 而我們的圖片在
fetch
的時候,await fetch('https://api.thecatapi.com/v1/images/search', { next: { tags: ['refresh'] } })
,指定了tag
為refresh
- 因此可以聯(lián)動做到重新驗(yàn)證。
1.3 緩存的退出方式
上面說的都是重新驗(yàn)證,說白了就是緩存的一種刷新機(jī)制,那么我們是否有辦法主動退出緩存呢?
當(dāng)使用 fetch
的時候,若滿足以下情形,可以做到默認(rèn)退出緩存機(jī)制:
fetch
請求添加了cache: 'no-store'
或者revalidate: 0
屬性。例如
fetch('', { cache: 'no-store' })
fetch
請求在路由處理程序中并使用了其他方法,例如POST
。- 函數(shù)體內(nèi)使用到了
headers
或cookies
等方法。
export async function GET(request) {const token = request.cookies.get('token')return Response.json({ data: new Date().toLocaleTimeString() })
}
- 配置了路由段選項(xiàng)
const dynamic = 'force-dynamic'
export const dynamic = 'force-dynamic'
- 配置了路由段選項(xiàng)
fetchCache
,默認(rèn)會跳過緩存
二. Server Actions
Server Actions
是指在服務(wù)端執(zhí)行的異步函數(shù),但是可以在服務(wù)端和客戶端組件中使用。 我們什么情況下可以用到這個呢?
- 在
PageRouter
情況下,若需要前后端進(jìn)行交互,則需要先定義一個接口。 - 在
AppRouter
情況下,這種操作則可以簡化為Server Actions
。我們可以定義一個Server Actions
,然后直接在客戶端使用獲取數(shù)據(jù)。
它的基本定義方式就是通過 'use server'
聲明,一般分為兩種:
- 模塊級別:在文件的頂部聲明,那么該文件下聲明的所有導(dǎo)出函數(shù)都是
Server Actions
。 - 函數(shù)級別:在函數(shù)內(nèi)部的頂端添加聲明,那么只有該函數(shù)是
Server Actions
。
例如:
// 函數(shù)級別
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
的方式從后端取數(shù)據(jù)。
'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
文件夾:
里面的函數(shù)如下:
'use server'
export async function getData() {const res = await fetch('https://jsonplaceholder.typicode.com/posts')const data = await res.json()return data;
}
然后在組件中使用:服務(wù)端組件和客戶端組件都可以使用
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
代碼更加簡潔,無需手動創(chuàng)建一個接口。并且這個函數(shù)可以在代碼的任何一個地方使用。