突變
總覽
突變用於將資料更新傳送至伺服器,並將變更套用至本機快取。突變也可以使快取資料失效,並強制重新擷取。
定義突變端點
突變端點定義為在 createApi
的 endpoints
區段內傳回一個物件,並使用 build.mutation()
方法定義欄位。
突變端點應定義一個建構 URL(包括任何 URL 查詢參數)的 query
回呼,或定義一個可以執行任意非同步邏輯並傳回結果的 queryFn
回呼。query
回呼也可以傳回一個包含 URL、要使用的 HTTP 方法和請求主體的物件。
如果 query
回呼需要額外的資料來產生 URL,應寫成只接受一個參數。如果您需要傳遞多個參數,請將它們格式化為單一的「選項物件」。
變異端點也可能在快取結果之前修改回應內容,定義「標籤」來識別快取失效,並提供快取項目生命週期回呼,以便在新增和移除快取項目時執行額外的邏輯。
與 TypeScript 搭配使用時,您應該提供回傳類型和預期的查詢參數的泛型:build.mutation<ReturnType, ArgType>
。如果沒有參數,請改為使用 void
作為 arg 類型。
- TypeScript
- JavaScript
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,
}
) {},
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
// The mutation accepts a `Partial<Post>` arg, and returns a `Post`
updatePost: build.mutation({
// 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, meta, arg) => response.data,
// Pick out errors and prevent nested properties in a hook or selector
transformErrorResponse: (response, 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
會回傳一個元組。元組中的第一個項目是「觸發」函式,第二個元素包含一個包含 status
、error
和 data
的物件。
與 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
元件,讓我們可以編輯貼文名稱。
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 服務 範例。這實作了 有選擇性地失效清單 策略,且很可能成為實際應用程式的良好基礎。
- TypeScript
- JavaScript
// 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
// Or from '@reduxjs/toolkit/query' if not using the auto-generated hooks
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const postApi = createApi({
reducerPath: 'postsApi',
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query({
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 })),
{ 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({
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({
query: (id) => `post/${id}`,
providesTags: (result, error, id) => [{ type: 'Posts', id }],
}),
updatePost: build.mutation({
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({
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