查詢
總覽
這是 RTK Query 最常見的用例。查詢操作可以使用您選擇的任何資料擷取函式庫執行,但一般建議您僅將查詢用於擷取資料的請求。對於任何會變更伺服器上資料或可能會使快取失效的動作,您都應該使用變異。
預設情況下,RTK Query 會附帶 fetchBaseQuery
,這是一個輕量級的 fetch
封裝器,它會自動處理請求標頭和回應解析,類似於 axios
等常見函式庫。如果你對 fetchBaseQuery
不滿意,請參閱 自訂查詢。
根據你的環境,如果你選擇使用 fetchBaseQuery
或單獨使用 fetch
,你可能需要使用 node-fetch
或 cross-fetch
來填充 fetch
。
請參閱 useQuery
以取得掛鉤簽章和其他詳細資訊。
定義查詢端點
查詢端點是透過在 createApi
的 endpoints
區段內傳回一個物件,並使用 builder.query()
方法定義欄位來定義的。
查詢端點應定義一個 query
回呼函式來建構 URL (包括任何 URL 查詢參數),或 一個 queryFn
回呼函式,它可以執行任意非同步邏輯並傳回結果。
如果 query
回呼函式需要額外的資料來產生 URL,則應寫成只接受一個引數。如果你需要傳入多個參數,請將它們格式化為單一的「選項物件」。
查詢端點也可以在結果快取之前修改回應內容,定義「標籤」來識別快取失效,並提供快取項目生命週期回呼函式,以便在新增和移除快取項目時執行額外的邏輯。
與 TypeScript 搭配使用時,你應提供回傳類型和預期的查詢引數的泛型:build.query<ReturnType, ArgType>
。如果沒有引數,請改為使用 void
作為引數類型。
- TypeScript
- JavaScript
// Or from '@reduxjs/toolkit/query/react'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
// The query accepts a number and returns a Post
getPost: build.query<Post, number>({
// note: an optional `queryFn` may be used in place of `query`
query: (id) => ({ url: `post/${id}` }),
// 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,
providesTags: (result, error, id) => [{ type: 'Post', id }],
// The 2nd parameter is the destructured `QueryLifecycleApi`
async onQueryStarted(
arg,
{
dispatch,
getState,
extra,
requestId,
queryFulfilled,
getCacheEntry,
updateCachedData,
}
) {},
// The 2nd parameter is the destructured `QueryCacheLifecycleApi`
async onCacheEntryAdded(
arg,
{
dispatch,
getState,
extra,
requestId,
cacheEntryRemoved,
cacheDataLoaded,
getCacheEntry,
updateCachedData,
}
) {},
}),
}),
})
// Or from '@reduxjs/toolkit/query/react'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({
baseUrl: '/',
}),
tagTypes: ['Post'],
endpoints: (build) => ({
// The query accepts a number and returns a Post
getPost: build.query({
// note: an optional `queryFn` may be used in place of `query`
query: (id) => ({ url: `post/${id}` }),
// 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,
providesTags: (result, error, id) => [{ type: 'Post', id }],
// The 2nd parameter is the destructured `QueryLifecycleApi`
async onQueryStarted(
arg,
{
dispatch,
getState,
extra,
requestId,
queryFulfilled,
getCacheEntry,
updateCachedData,
}
) {},
// The 2nd parameter is the destructured `QueryCacheLifecycleApi`
async onCacheEntryAdded(
arg,
{
dispatch,
getState,
extra,
requestId,
cacheEntryRemoved,
cacheDataLoaded,
getCacheEntry,
updateCachedData,
}
) {},
}),
}),
})
使用 React Hooks 執行查詢
如果您使用 React Hooks,RTK Query 會為您執行一些額外操作。主要優點是您會取得最佳化呈現的 Hook,讓您可以進行「背景擷取」,以及方便使用的衍生布林值。
系統會根據服務定義中endpoint
的名稱自動產生 Hooks。具有getPost: builder.query()
的端點欄位會產生名為useGetPostQuery
的 Hook。
Hook 類型
有 5 個與查詢相關的 Hooks
useQuery
- 組合
useQuerySubscription
和useQueryState
,是主要的 Hook。會自動觸發從端點擷取資料、讓元件「訂閱」快取資料,以及從 Redux 儲存庫讀取要求狀態和快取資料。
- 組合
useQuerySubscription
- 傳回
refetch
函式並接受所有 Hook 選項。會自動觸發從端點擷取資料,並讓元件「訂閱」快取資料。
- 傳回
useQueryState
- 傳回查詢狀態並接受
skip
和selectFromResult
。從 Redux 儲存庫讀取要求狀態和快取資料。
- 傳回查詢狀態並接受
useLazyQuery
- 傳回包含
trigger
函式、查詢結果和最後承諾資訊的組。類似於useQuery
,但可以手動控制資料擷取發生的時間。請注意:trigger
函式會在您想要在已有快取資料時略過提出要求的情況下,取得preferCacheValue?: boolean
的第二個引數。
- 傳回包含
useLazyQuerySubscription
- 傳回包含
trigger
函式和最後承諾資訊的組。類似於useQuerySubscription
,但可以手動控制資料擷取發生的時間。請注意:trigger
函式會在您想要在已有快取資料時略過提出要求的情況下,取得preferCacheValue?: boolean
的第二個引數。
- 傳回包含
實際上,標準的基於useQuery
的 Hooks(例如useGetPostQuery
)將會是應用程式中使用的主要 Hooks,但其他 Hooks 可用於特定使用案例。
查詢 Hook 選項
查詢掛鉤預期兩個參數:(queryArg?, queryOptions?)
。
queryArg
參數將傳遞到底層 query
回呼以產生 URL。
queryOptions
物件接受幾個額外的參數,可用於控制資料擷取的行為
- skip - 允許查詢在該渲染中「跳過」執行。預設為
false
- pollingInterval - 允許查詢在提供的間隔(以毫秒為單位)自動重新擷取。預設為
0
(關閉) - selectFromResult - 允許變更掛鉤的傳回值以取得結果的子集,針對傳回的子集進行渲染最佳化。
- refetchOnMountOrArgChange - 允許強制查詢在掛載時總是重新擷取(提供
true
時)。允許強制查詢重新擷取,如果自上次查詢相同快取以來已過足夠時間(以秒為單位)(提供數字
時)。預設為false
- refetchOnFocus - 允許強制查詢在瀏覽器視窗重新取得焦點時重新擷取。預設為
false
- refetchOnReconnect - 允許強制查詢在重新取得網路連線時重新擷取。預設為
false
所有與 refetch
相關的選項都會覆寫您可能已在 createApi 中設定的預設值
常使用的查詢掛鉤傳回值
查詢掛鉤傳回一個包含查詢請求最新 data
等屬性的物件,以及當前請求生命週期狀態的狀態布林值。以下是部分最常使用的屬性。請參閱 useQuery
以取得所有傳回屬性的完整清單。
data
- 最新傳回的結果,不論掛鉤參數為何,如果存在的話。currentData
- 最新傳回的結果,針對目前的掛鉤參數,如果存在的話。error
- 錯誤結果,如果存在的話。isUninitialized
- 為 true 時,表示查詢尚未開始。isLoading
- 為 true 時,表示查詢目前正在第一次載入,且尚未有資料。對於發出的第一個請求,這將為true
,但對於後續請求則不會。isFetching
- 為 true 時,表示查詢目前正在擷取,但可能擁有來自先前請求的資料。對於發出的第一個請求,以及後續請求,這將為true
。isSuccess
- 當為 true 時,表示查詢有來自成功請求的資料。isError
- 當為 true 時,表示查詢處於error
狀態。refetch
- 強制重新擷取查詢的函式
在大部分情況下,你可能會讀取 data
以及 isLoading
或 isFetching
以呈現你的使用者介面。
查詢 Hook 使用範例
以下是 PostDetail
元件的範例
export const PostDetail = ({ id }: { id: string }) => {
const {
data: post,
isFetching,
isLoading,
} = useGetPostQuery(id, {
pollingInterval: 3000,
refetchOnMountOrArgChange: true,
skip: false,
})
if (isLoading) return <div>Loading...</div>
if (!post) return <div>Missing post!</div>
return (
<div>
{post.name} {isFetching ? '...refetching' : ''}
</div>
)
}
這個元件設定的方式會有一些不錯的特質
- 它只在初始載入時顯示「Loading...」
- 初始載入定義為一個正在處理且快取中沒有資料的查詢
- 當請求因輪詢間隔而重新觸發時,它會在貼文名稱中加入「...refetching」
- 如果使用者關閉這個
PostDetail
,但在允許的時間內重新開啟它,他們會立即收到快取的結果,而且輪詢會以先前的行為繼續。
查詢載入狀態
由 createApi
的 React 特定版本產生的自動產生 React hook 提供衍生布林值,用以反映特定查詢的當前狀態。衍生布林值是產生的 React hook 的首選,而不是 status
旗標,因為衍生布林值能夠提供更多細節,而這在單一 status
旗標中是不可能的,因為多個狀態可能在特定時間為 true(例如 isFetching
和 isSuccess
)。
對於查詢端點,RTK Query 在 isLoading
和 isFetching
之間維持語意區別,以提供更多彈性,並提供衍生資訊。
isLoading
指的是查詢對特定 hook 來說是第一次進行中。此時不會有資料可用。isFetching
指的是查詢對特定端點 + 查詢參數組合進行中,但不一定是第一次。資料可能來自這個 hook 之前執行的請求,也許是使用前一個查詢參數。
此區別允許在處理 UI 行為時有更大的控制權。例如,isLoading
可用於在第一次載入時顯示一個骨架,而 isFetching
可用於在從第 1 頁變更到第 2 頁或資料無效並重新擷取時將舊資料灰顯。
import { Skeleton } from './Skeleton'
import { useGetPostsQuery } from './api'
function App() {
const { data = [], isLoading, isFetching, isError } = useGetPostsQuery()
if (isError) return <div>An error has occurred!</div>
if (isLoading) return <Skeleton />
return (
<div className={isFetching ? 'posts--disabled' : ''}>
{data.map((post) => (
<Post
key={post.id}
id={post.id}
name={post.name}
disabled={isFetching}
/>
))}
</div>
)
}
雖然預期 data
會用於大多數情況,但也會提供 currentData
,這允許進一步的詳細程度。例如,如果您想在 UI 中以半透明方式顯示資料以表示重新擷取狀態,您可以將 data
與 isFetching
結合使用來達成此目的。但是,如果您還希望僅顯示對應於目前 arg 的資料,您可以改用 currentData
來達成此目的。
在以下範例中,如果第一次擷取文章,將會顯示載入骨架。如果目前使用者的文章先前已擷取,而且正在重新擷取(例如,因為突變),UI 將會顯示先前的資料,但會將資料灰顯。如果使用者變更,它將會再次顯示骨架,而不是將先前使用者的資料灰顯。
import { Skeleton } from './Skeleton'
import { useGetPostsByUserQuery } from './api'
function PostsList({ userName }: { userName: string }) {
const { currentData, isFetching, isError } = useGetPostsByUserQuery(userName)
if (isError) return <div>An error has occurred!</div>
if (isFetching && !currentData) return <Skeleton />
return (
<div className={isFetching ? 'posts--disabled' : ''}>
{currentData
? currentData.map((post) => (
<Post
key={post.id}
id={post.id}
name={post.name}
disabled={isFetching}
/>
))
: 'No data available'}
</div>
)
}
查詢快取金鑰
當您執行查詢時,RTK Query 會自動將請求參數序列化,並為請求建立內部 queryCacheKey
。任何產生相同 queryCacheKey
的未來請求將會與原始請求重複刪除,並且如果從任何訂閱元件的查詢觸發 refetch
,將會共用更新。
從查詢結果中選取資料
有時您可能有一個訂閱查詢的父元件,然後在子元件中,您想從該查詢中選取一個項目。在大多數情況下,當您知道自己已經有結果時,您不希望對 getItemById
類型的查詢執行額外的請求。
selectFromResult
允許您以高效率的方式從查詢結果中取得特定區段。使用此功能時,除非所選項目的基礎資料已變更,否則元件不會重新渲染。如果所選項目是較大集合中的其中一個元素,它將忽略對同一個集合中元素的變更。
function PostsList() {
const { data: posts } = api.useGetPostsQuery()
return (
<ul>
{posts?.data?.map((post) => <PostById key={post.id} id={post.id} />)}
</ul>
)
}
function PostById({ id }: { id: number }) {
// Will select the post with the given id, and will only rerender if the given post's data changes
const { post } = api.useGetPostsQuery(undefined, {
selectFromResult: ({ data }) => ({
post: data?.find((post) => post.id === id),
}),
})
return <li>{post?.name}</li>
}
請注意,會對 selectFromResult
的整體傳回值執行淺層相等性檢查,以確定是否強制重新渲染。亦即,如果傳回的任何物件值變更參照,它將觸發重新渲染。如果在回呼中建立並使用新的陣列/物件作為傳回值,由於每次執行回呼時都會被識別為新項目,這將會阻礙效能優勢。當有意提供空陣列/物件時,為了避免每次執行回呼時重新建立它,您可以在元件外部宣告一個空陣列/物件,以維持穩定的參照。
// An array declared here will maintain a stable reference rather than be re-created again
const emptyArray: Post[] = []
function PostsList() {
// This call will result in an initial render returning an empty array for `posts`,
// and a second render when the data is received.
// It will trigger additional rerenders only if the `posts` data changes
const { posts } = api.useGetPostsQuery(undefined, {
selectFromResult: ({ data }) => ({
posts: data ?? emptyArray,
}),
})
return (
<ul>
{posts.map((post) => (
<PostById key={post.id} id={post.id} />
))}
</ul>
)
}
總結上述行為 - 傳回值必須正確備忘。另請參閱 使用選擇器衍生資料 和 Redux Essentials - RTK Query 進階模式 以取得更多資訊。
避免不必要的請求
預設情況下,如果您新增一個與現有元件進行相同查詢的元件,將不會執行任何請求。
在某些情況下,您可能想要略過此行為並強制重新擷取 - 在這種情況下,您可以呼叫掛鉤回傳的 refetch
。
如果您沒有使用 React Hooks,您可以像這樣存取 refetch
const { status, data, error, refetch } = dispatch(
pokemonApi.endpoints.getPokemon.initiate('bulbasaur'),
)
範例:觀察快取行為
此範例示範請求重複資料刪除和快取行為
- 第一個
Pokemon
元件掛載並立即擷取 'bulbasaur' - 一秒後,另一個
Pokemon
元件使用 'bulbasaur' 呈現。- 請注意,這個元件從未顯示 'Loading...',也沒有發生新的網路請求?它在此處使用快取。
- 稍後,新增一個 'pikachu' 的
Pokemon
元件,並執行新的請求。 - 當您為特定
Pokemon
按一下 'Refetch' 時,它將使用一個請求更新該Pokemon
的所有執行個體。
按一下 '新增 bulbasaur' 按鈕。您將觀察到上述相同的行為,直到您按一下其中一個元件上的 'Refetch' 按鈕。