手动缓存更新
概述
在大多数情况下,为了在触发后端更改后接收最新数据,您可以利用缓存标签失效来执行自动重新获取。这将导致查询在被告知发生会导致其数据过时的变异时重新获取其数据。
我们建议在大多数情况下优先使用自动重新获取而不是手动缓存更新。
但是,确实存在需要手动缓存更新的用例,例如“乐观”或“悲观”更新,或在缓存条目生命周期中修改数据。
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)
查询创建新的缓存条目,从而避免以后进行额外的获取以检索该项目。
食谱
乐观更新
当您希望在触发 mutation
后立即对缓存数据执行更新时,您可以应用 乐观更新
。当您希望让用户感觉他们的更改是立即发生的,即使变异请求仍在进行中时,这可能是一个有用的模式。
乐观更新的核心概念是
- 当你开始一个查询或变异时,
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
来手动更新缓存数据,使用来自服务器的响应中的数据进行草稿更新 - 你通过在
onQueryStarted
内分发api.util.upsertQueryData
来手动创建一个新的缓存条目,使用后端返回的完整 Post 对象。
- 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
方法的地方执行此操作,包括通过useDispatch钩子(或像useAppDispatch这样的类型化版本,适用于 TypeScript 用户)在 React 组件中。
通常,您应该避免在没有充分理由的情况下,在变异的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>
}