手動快取更新
概觀
在大部分情況下,若要接收後端觸發變更後的最新資料,您可以利用快取標籤失效來執行自動重新擷取。這將導致查詢在得知會導致其資料過期的突變發生後重新擷取其資料。
我們建議在大部分情況下優先使用自動重新擷取,而非手動快取更新。
然而,確實有必要進行手動快取更新的用例,例如「樂觀」或「悲觀」更新,或修改資料作為快取條目生命週期的部分。
RTK Query 匯出 thunk 以供這些用例使用,附加至 api.utils
updateQueryData
:更新已存在的快取條目upsertQueryData
:建立或取代快取條目
由於這些是 thunk,因此您可以在任何有權存取 dispatch
的地方派送它們。
更新現有快取項目
對於現有快取項目的更新,請使用 updateQueryData
。
updateQueryData
嚴格來說是專門用於對現有快取項目執行更新,而不是建立新項目。如果派送 updateQueryData
thunk 動作,且 endpointName
+ args
組合與任何現有快取項目不符,則不會呼叫提供的 recipe
回呼,也不會傳回 patches
或 inversePatches
。
手動更新快取項目的使用案例
- 當嘗試進行突變時,立即提供使用者回饋
- 在突變後,更新已快取的大量項目清單中的單一項目,而不是重新擷取整個清單
- 延遲大量突變,並提供立即回饋,彷彿正在套用這些突變,然後傳送單一要求至伺服器,以更新延遲的嘗試
建立新快取項目或取代現有快取項目
若要建立或取代現有快取項目,請使用 upsertQueryData
。
upsertQueryData
用於對現有快取項目執行取代或建立新快取項目。由於 upsertQueryData
無法存取快取項目的先前狀態,因此只能將更新執行為取代。相較之下,updateQueryData
允許修補現有快取項目,但無法建立新的快取項目。
一個範例使用案例為 悲觀更新。如果客戶端發出 API 呼叫以建立 Post
,後端可能會傳回其完整資料,包括 id
。然後,我們可以使用 upsertQueryData
為 getPostById(id)
查詢建立新的快取項目,避免稍後進行額外擷取以檢索項目。
食譜
樂觀更新
當您希望在觸發 突變
之後立即更新快取資料時,您可以套用樂觀更新
。當您希望讓使用者感覺到他們的變更是立即的,即使突變要求仍在傳輸中,這是一個有用的模式。
樂觀更新的核心概念是
- 當您開始查詢或變更時,將執行
onQueryStarted
- 您手動透過在
onQueryStarted
內派送api.util.updateQueryData
更新快取資料 - 然後,在
queryFulfilled
拒絕的情況下- 您透過從先前派送取得的物件的
.undo
屬性將其還原,或 - 您透過
api.util.invalidateTags
使快取資料失效,以觸發資料的完整重新擷取
- 您透過從先前派送取得的物件的
在許多變更可能在短時間內觸發,導致重疊要求的情況下,如果您嘗試使用 .undo
屬性在失敗時還原修補程式,您可能會遇到競爭情況。對於這些情況,通常最簡單且最安全的方法是在發生錯誤時使標籤失效,並從伺服器重新擷取真正最新的資料。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: ['Post'],
}),
updatePost: build.mutation<void, Pick<Post, 'id'> & Partial<Post>>({
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch)
})
)
try {
await queryFulfilled
} catch {
patchResult.undo()
/**
* Alternatively, on failure you can invalidate the corresponding cache tags
* to trigger a re-fetch:
* dispatch(api.util.invalidateTags(['Post']))
*/
}
},
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
getPost: build.query({
query: (id) => `post/${id}`,
providesTags: ['Post'],
}),
updatePost: build.mutation({
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch)
})
)
try {
await queryFulfilled
} catch {
patchResult.undo()
/**
* Alternatively, on failure you can invalidate the corresponding cache tags
* to trigger a re-fetch:
* dispatch(api.util.invalidateTags(['Post']))
*/
}
},
}),
}),
})
或者,如果您偏好使用 .catch
的較短版本
- async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
+ onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, patch)
})
)
- try {
- await queryFulfilled
- } catch {
- patchResult.undo()
- }
+ queryFulfilled.catch(patchResult.undo)
}
範例
悲觀更新
當您希望在觸發 變更
後根據從伺服器接收的回應更新快取資料時,您可以套用 悲觀更新
。悲觀更新
和 樂觀更新
之間的區別在於,悲觀更新
會等到伺服器的回應後才更新快取資料。
悲觀更新的核心概念是
- 當您開始查詢或變更時,將執行
onQueryStarted
- 您等待
queryFulfilled
解析為包含伺服器轉換回應的物件,其中data
屬性包含資料 - 您手動透過在
onQueryStarted
內派送api.util.updateQueryData
更新快取資料,使用伺服器回應中的資料作為您的草稿更新 - 您可以使用後端傳回的完整 Post 物件,在
onQueryStarted
內派送api.util.upsertQueryData
來手動建立新的快取項目。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: ['Post'],
}),
updatePost: build.mutation<Post, Pick<Post, 'id'> & Partial<Post>>({
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
try {
const { data: updatedPost } = await queryFulfilled
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, updatedPost)
})
)
} catch {}
},
}),
createPost: build.mutation<Post, Pick<Post, 'id'> & Partial<Post>>({
query: ({ id, ...body }) => ({
url: `post/${id}`,
method: 'POST',
body,
}),
async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
try {
const { data: createdPost } = await queryFulfilled
const patchResult = dispatch(
api.util.upsertQueryData('getPost', id, createdPost)
)
} catch {}
},
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
getPost: build.query({
query: (id) => `post/${id}`,
providesTags: ['Post'],
}),
updatePost: build.mutation({
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
async onQueryStarted({ id, ...patch }, { dispatch, queryFulfilled }) {
try {
const { data: updatedPost } = await queryFulfilled
const patchResult = dispatch(
api.util.updateQueryData('getPost', id, (draft) => {
Object.assign(draft, updatedPost)
})
)
} catch {}
},
}),
createPost: build.mutation({
query: ({ id, ...body }) => ({
url: `post/${id}`,
method: 'POST',
body,
}),
async onQueryStarted({ id }, { dispatch, queryFulfilled }) {
try {
const { data: createdPost } = await queryFulfilled
const patchResult = dispatch(
api.util.upsertQueryData('getPost', id, createdPost)
)
} catch {}
},
}),
}),
})
一般更新
如果您希望在應用程式中的其他地方更新快取資料,您可以在任何有權存取 store.dispatch
方法的地方執行此動作,包括在 React 組件中透過 useDispatch 鉤子(或針對 TypeScript 使用者輸入的類型化版本,例如 useAppDispatch)。
一般來說,您應該避免在沒有正當理由的情況下,在突變的 onQueryStarted
回呼之外手動更新快取,因為 RTK Query 的目的是將快取資料視為伺服器端狀態的反映。
import { api } from './api'
import { useAppDispatch } from './store/hooks'
function App() {
const dispatch = useAppDispatch()
function handleClick() {
/**
* This will update the cache data for the query corresponding to the `getPosts` endpoint,
* when that endpoint is used with no argument (undefined).
*/
const patchCollection = dispatch(
api.util.updateQueryData('getPosts', undefined, (draftPosts) => {
draftPosts.push({ id: 1, name: 'Teddy' })
}),
)
}
return <button onClick={handleClick}>Add post to cache</button>
}