Usage > Mutations: sending updates to the server">Usage > Mutations: sending updates to the server">
跳至主要內容

突變

總覽

突變用於將資料更新傳送至伺服器,並將變更套用至本機快取。突變也可以使快取資料失效,並強制重新擷取。

定義突變端點

突變端點定義為在 createApiendpoints 區段內傳回一個物件,並使用 build.mutation() 方法定義欄位。

突變端點應定義一個建構 URL(包括任何 URL 查詢參數)的 query 回呼,或定義一個可以執行任意非同步邏輯並傳回結果的 queryFn 回呼query 回呼也可以傳回一個包含 URL、要使用的 HTTP 方法和請求主體的物件。

如果 query 回呼需要額外的資料來產生 URL,應寫成只接受一個參數。如果您需要傳遞多個參數,請將它們格式化為單一的「選項物件」。

變異端點也可能在快取結果之前修改回應內容,定義「標籤」來識別快取失效,並提供快取項目生命週期回呼,以便在新增和移除快取項目時執行額外的邏輯。

與 TypeScript 搭配使用時,您應該提供回傳類型和預期的查詢參數的泛型:build.mutation<ReturnType, ArgType>。如果沒有參數,請改為使用 void 作為 arg 類型。

所有變異端點選項的範例
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './types'

const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
// The mutation accepts a `Partial<Post>` arg, and returns a `Post`
updatePost: build.mutation<Post, Partial<Post> & Pick<Post, 'id'>>({
// note: an optional `queryFn` may be used in place of `query`
query: ({ id, ...patch }) => ({
url: `post/${id}`,
method: 'PATCH',
body: patch,
}),
// Pick out data and prevent nested properties in a hook or selector
transformResponse: (response: { data: Post }, meta, arg) => response.data,
// Pick out errors and prevent nested properties in a hook or selector
transformErrorResponse: (
response: { status: string | number },
meta,
arg
) => response.status,
invalidatesTags: ['Post'],
// onQueryStarted is useful for optimistic updates
// The 2nd parameter is the destructured `MutationLifecycleApi`
async onQueryStarted(
arg,
{ dispatch, getState, queryFulfilled, requestId, extra, getCacheEntry }
) {},
// The 2nd parameter is the destructured `MutationCacheLifecycleApi`
async onCacheEntryAdded(
arg,
{
dispatch,
getState,
extra,
requestId,
cacheEntryRemoved,
cacheDataLoaded,
getCacheEntry,
}
) {},
}),
}),
})
資訊

onQueryStarted 方法可用於 樂觀更新

使用 React Hooks 執行變異

變異 Hook 行為

useQuery 不同,useMutation 會回傳一個元組。元組中的第一個項目是「觸發」函式,第二個元素包含一個包含 statuserrordata 的物件。

useQuery hook 不同,useMutation hook 不會自動執行。要執行變異,您必須呼叫從 hook 回傳的觸發函式作為第一個元組值。

請參閱 useMutation 以取得 hook 簽章和額外詳細資料。

常用變異 Hook 回傳值

useMutation hook 回傳一個包含「變異觸發」函式和包含「變異結果」屬性的物件的元組。

「變異觸發」是一個函式,當呼叫時,將觸發該端點的變異請求。呼叫「變異觸發」會回傳一個具有 unwrap 屬性的承諾,可以呼叫它來解開變異呼叫並提供原始回應/錯誤。如果您希望在呼叫站點內聯判斷變異是否成功/失敗,這將很有用。

「變異結果」是一個包含屬性的物件,例如變異要求的最新 資料,以及目前要求生命週期狀態的狀態布林值。

以下是「變異結果」物件上一些最常使用的屬性。請參閱 useMutation 以取得所有回傳屬性的完整清單。

  • 資料 - 如果存在,從最新觸發回應回傳的資料。如果呼叫來自相同掛勾實例的後續觸發,這將回傳未定義,直到收到新資料。如果需要先前的回應資料以順利轉換為新資料,請考慮元件層級快取。
  • 錯誤 - 如果存在,錯誤結果。
  • 未初始化 - 如果為 true,表示變異尚未觸發。
  • 正在載入 - 如果為 true,表示變異已觸發且正在等待回應。
  • 成功 - 如果為 true,表示最後觸發的變異有來自成功要求的資料。
  • 錯誤 - 如果為 true,表示最後觸發的變異導致錯誤狀態。
  • 重設 - 一個方法,用於將掛勾重設回其原始狀態並從快取中移除目前的結果
注意

使用 RTK Query 時,變異在「載入」和「擷取」之間沒有語義區別,與 查詢 的方式不同。對於變異,後續呼叫不會假設一定相關,因此變異不是「載入」就是「未載入」,沒有「重新擷取」的概念。

共用變異結果

預設情況下,useMutation 掛勾的個別實例彼此之間沒有內在關聯。觸發一個實例不會影響個別實例的結果。這適用於在同一個元件或不同元件中呼叫掛勾的情況。

export const ComponentOne = () => {
// Triggering `updatePostOne` will affect the result in this component,
// but not the result in `ComponentTwo`, and vice-versa
const [updatePost, result] = useUpdatePostMutation()

return <div>...</div>
}

export const ComponentTwo = () => {
const [updatePost, result] = useUpdatePostMutation()

return <div>...</div>
}

RTK Query 提供一個選項,可使用 fixedCacheKey 選項在變異掛勾實例間共用結果。任何具有相同 fixedCacheKey 字串的 useMutation 掛勾,在任何觸發函式被呼叫時,都會彼此共用結果。這應該是你希望共用結果的每個變異掛勾實例間共用的唯一字串。

export const ComponentOne = () => {
// Triggering `updatePostOne` will affect the result in both this component,
// but as well as the result in `ComponentTwo`, and vice-versa
const [updatePost, result] = useUpdatePostMutation({
fixedCacheKey: 'shared-update-post',
})

return <div>...</div>
}

export const ComponentTwo = () => {
const [updatePost, result] = useUpdatePostMutation({
fixedCacheKey: 'shared-update-post',
})

return <div>...</div>
}
注意

使用 fixedCacheKey 時,originalArgs 屬性無法共用,且永遠會是 undefined

標準變異範例

這是你可以於頁面底部看到的完整範例的修改版本,用於突顯 updatePost 變異。在此情況下,一則貼文會透過 useQuery 擷取,然後會呈現一個 EditablePostName 元件,讓我們可以編輯貼文名稱。

src/features/posts/PostDetail.tsx
export const PostDetail = () => {
const { id } = useParams<{ id: any }>()

const { data: post } = useGetPostQuery(id)

const [
updatePost, // This is the mutation trigger
{ isLoading: isUpdating }, // This is the destructured mutation result
] = useUpdatePostMutation()

return (
<Box p={4}>
<EditablePostName
name={post.name}
onUpdate={(name) => {
// If you want to immediately access the result of a mutation, you need to chain `.unwrap()`
// if you actually want the payload or to catch the error.
// Example: `updatePost().unwrap().then(fulfilled => console.log(fulfilled)).catch(rejected => console.error(rejected))

return (
// Execute the trigger with the `id` and updated `name`
updatePost({ id, name })
)
}}
isLoading={isUpdating}
/>
</Box>
)
}

進階變異與重新驗證

在實際情況中,開發人員通常會希望在執行變異(又稱「重新驗證」)後,將其本機資料快取與伺服器重新同步。RTK Query 採用更集中化的方式來處理這項工作,並要求你在 API 服務定義中設定失效行為。請參閱 使用抽象標籤 ID 的進階失效,以取得有關使用 RTK Query 進行進階失效處理的詳細資訊。

重新驗證範例

這是貼文的 CRUD 服務 範例。這實作了 有選擇性地失效清單 策略,且很可能成為實際應用程式的良好基礎。

src/app/services/posts.ts
// Or from '@reduxjs/toolkit/query' if not using the auto-generated hooks
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'

export interface Post {
id: number
name: string
}

type PostsResponse = Post[]

export const postApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => 'posts',
// Provides a list of `Posts` by `id`.
// If any mutation is executed that `invalidate`s any of these tags, this query will re-run to be always up-to-date.
// The `LIST` id is a "virtual id" we just made up to be able to invalidate this query specifically if a new `Posts` element was added.
providesTags: (result) =>
// is result available?
result
? // successful query
[
...result.map(({ id }) => ({ type: 'Posts', id } as const)),
{ type: 'Posts', id: 'LIST' },
]
: // an error occurred, but we still want to refetch this query when `{ type: 'Posts', id: 'LIST' }` is invalidated
[{ type: 'Posts', id: 'LIST' }],
}),
addPost: build.mutation<Post, Partial<Post>>({
query(body) {
return {
url: `post`,
method: 'POST',
body,
}
},
// Invalidates all Post-type queries providing the `LIST` id - after all, depending of the sort order,
// that newly created post could show up in any lists.
invalidatesTags: [{ type: 'Posts', id: 'LIST' }],
}),
getPost: build.query<Post, number>({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
updatePost: build.mutation<Post, Partial<Post>>({
query(data) {
const { id, ...body } = data
return {
url: `post/${id}`,
method: 'PUT',
body,
}
},
// Invalidates all queries that subscribe to this Post `id` only.
// In this case, `getPost` will be re-run. `getPosts` *might* rerun, if this id was under its results.
invalidatesTags: (result, error, { id }) => [{ type: 'Posts', id }],
}),
deletePost: build.mutation<{ success: boolean; id: number }, number>({
query(id) {
return {
url: `post/${id}`,
method: 'DELETE',
}
},
// Invalidates all queries that subscribe to this Post `id` only.
invalidatesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
}),
})

export const {
useGetPostsQuery,
useAddPostMutation,
useGetPostQuery,
useUpdatePostMutation,
useDeletePostMutation,
} = postApi