跳到主要内容

 

手动缓存更新

概述

在大多数情况下,为了在后端触发更改后接收最新的数据,你可以利用缓存标签失效来执行自动重新获取。这将导致查询在被告知发生了会使其数据过时的变更后重新获取其数据。

我们建议在大多数情况下优先使用自动重新获取而不是手动缓存更新。

然而,_确实有_手动缓存更新必要的使用场景,例如"乐观"或"悲观"更新,或者作为缓存条目生命周期的一部分修改数据。

RTK Query为这些使用场景导出了thunks,附加到api.utils

由于这些是thunks,你可以在任何有访问dispatch的地方分派它们。

更新现有缓存条目

对于现有缓存条目的更新,使用updateQueryData

updateQueryData严格用于对现有缓存条目执行_更新_,而不是创建新条目。如果分派了一个updateQueryData thunk操作,并且endpointName + args组合不匹配任何现有的缓存条目,那么提供的recipe回调将不会被调用,也不会返回任何patchesinversePatches

手动更新缓存条目的使用场景:

  • 当尝试进行变更时,向用户提供即时反馈
  • 在变更后,更新已经缓存的大量项目列表中的单个项目,而不是重新获取整个列表
  • 通过立即反馈,将大量的变更进行防抖处理,就像它们正在被应用一样,然后向服务器发送单个请求来更新防抖尝试

创建新的缓存条目或替换现有的

要创建或替换现有的缓存条目,使用upsertQueryData

upsertQueryData旨在对现有的缓存条目执行_替换_或_创建_新的。由于upsertQueryData无法访问缓存条目的先前状态,更新可能只能作为替换进行。相比之下,updateQueryData允许对现有的缓存条目进行修补,但不能创建新的。

一个示例使用场景是悲观更新。如果客户端发起一个API调用来创建一个Post,后端可能会返回其完整的数据,包括id。然后我们可以使用upsertQueryDatagetPostById(id)查询创建一个新的缓存条目,防止后续再次获取该项目。

配方

乐观更新

当你希望在触发mutation后立即对缓存数据进行更新时,你可以应用一个乐观更新。当你希望给用户的印象是他们的更改是即时的,即使变更请求仍在进行中,这可以是一个有用的模式。

乐观更新的核心概念是:

  • 当你开始一个查询或变更,onQueryStarted将被执行
  • 你通过分派api.util.updateQueryDataonQueryStarted中手动更新缓存数据
  • 然后,在queryFulfilled拒绝的情况下:
    • 你通过从早期分派得到的对象的.undo属性回滚它, 或者
    • 你通过api.util.invalidateTags使缓存数据无效,以触发数据的完全重新获取
提示

在许多变更可能在短时间内被触发,导致请求重叠的情况下,如果尝试使用.undo属性在失败时回滚补丁,你可能会遇到竞态条件。对于这些场景,最简单和最安全的方法通常是在错误时使标签无效,然后从服务器重新获取真正最新的数据。

乐观更新变更示例 (async await)
// 文件: types.ts noEmit
export interface Post {
id: number
name: string
}

// 文件: api.ts
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()

/**
* 或者,失败时你可以使相应的缓存标签无效
* 来触发重新获取:
* 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)
}

示例

React 乐观更新

悲观更新

当你希望在触发 mutation 后,根据从服务器收到的响应来更新缓存数据时,你可以应用一个 悲观更新悲观更新乐观更新 的区别在于,悲观更新 会等待服务器的响应后再更新缓存数据。

悲观更新的核心概念包括:

  • 当你开始一个查询或变异时,onQueryStarted 将被执行
  • 你等待 queryFulfilled 解析为一个对象,该对象包含服务器返回的转换响应在 data 属性中
  • 你通过在 onQueryStarted 中分派 api.util.updateQueryData,使用服务器响应中的数据手动更新缓存数据
  • 你通过在 onQueryStarted 中分派 api.util.upsertQueryData,使用后端返回的完整 Post 对象手动创建一个新的缓存条目。
悲观更新变异示例 (async await)
// 文件: types.ts noEmit
export interface Post {
id: number
name: string
}

// 文件: api.ts
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 {}
},
}),
}),
})

通用更新

如果你发现自己想在应用程序的其他地方更新缓存数据,只要你能访问到 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() {
/**
* 这将更新对应于 `getPosts` 端点的查询的缓存数据,
* 当该端点使用无参数(undefined)时。
*/
const patchCollection = dispatch(
api.util.updateQueryData('getPosts', undefined, (draftPosts) => {
draftPosts.push({ id: 1, name: 'Teddy' })
}),
)
}

return <button onClick={handleClick}>添加帖子到缓存</button>
}