自訂查詢
RTK Query 對於您的請求如何解析並不敏感。您可以使用任何您喜歡的函式庫來處理請求,也可以完全不使用函式庫。RTK Query 提供合理的預設值,預計涵蓋大部分使用案例,同時也允許自訂來調整查詢處理以符合特定需求。
使用 baseQuery
自訂查詢
處理查詢的預設方法是透過 baseQuery
選項在 createApi
上,搭配端點定義上的 query
選項。
要處理查詢,端點會定義一個 query
選項,它會將其回傳值傳遞給 API 使用的共用 baseQuery
函式。
預設情況下,RTK Query 會搭載 fetchBaseQuery
,這是一個輕量級的 fetch
封裝器,它會自動處理要求標頭和回應剖析,其方式類似於 axios
等常見函式庫。如果單獨使用 fetchBaseQuery
無法滿足你的需求,你可以使用封裝函式自訂其行為,或從頭建立你自己的 baseQuery
函式,供 createApi
使用。
另請參閱 baseQuery API 參考
。
實作自訂 baseQuery
RTK Query 預期 baseQuery
函式會使用三個引數呼叫:args
、api
和 extraOptions
。預期它會傳回一個物件,其中包含 data
或 error
屬性,或傳回此類物件的 Promise。
基本查詢和查詢函式務必自行捕捉錯誤,並在物件中傳回錯誤!
function brokenCustomBaseQuery() {
// ❌ Don't let this throw by itself
const data = await fetchSomeData()
return { data }
}
function correctCustomBaseQuery() {
// ✅ Catch errors and _return_ them so the RTKQ logic can track it
try {
const data = await fetchSomeData()
return { data }
} catch (error) {
return { error }
}
}
baseQuery 函式引數
const customBaseQuery = (
args,
{ signal, dispatch, getState },
extraOptions,
) => {
// omitted
}
baseQuery 函式傳回值
- 預期的成功結果格式
return { data: YourData }
- 預期的錯誤結果格式
return { error: YourError }
const customBaseQuery = (
args,
{ signal, dispatch, getState },
extraOptions,
) => {
if (Math.random() > 0.5) return { error: 'Too high!' }
return { data: 'All good!' }
}
此格式是必要的,以便 RTK Query 可以推斷你的回應傳回類型。
baseQuery
函式在核心上只需要有最小的傳回值才算有效;一個包含 data
或 error
屬性的物件。由使用者決定如何使用提供的引數,以及如何在函式內部處理要求。
fetchBaseQuery 預設值
特別是對於 fetchBaseQuery
,回傳類型如下
Promise<
| {
data: any
error?: undefined
meta?: { request: Request; response: Response }
}
| {
error: {
status: number
data: any
}
data?: undefined
meta?: { request: Request; response: Response }
}
>
- 預期的 fetchBaseQuery 成功結果格式
return { data: YourData }
- 預期的 fetchBaseQuery 錯誤結果格式
return { error: { status: number, data: YourErrorData } }
使用 transformResponse
自訂查詢回應
createApi
上的個別端點接受 transformResponse
屬性,允許在資料進入快取之前處理查詢或突變回傳的資料。
transformResponse
會使用成功 baseQuery
為對應端點回傳的資料呼叫,而 transformResponse
的回傳值會用作與該端點呼叫相關聯的快取資料。
預設情況下,伺服器上的酬載會直接回傳。
- TypeScript
- JavaScript
function defaultTransformResponse(
baseQueryReturnValue: unknown,
meta: unknown,
arg: unknown
) {
return baseQueryReturnValue
}
function defaultTransformResponse(baseQueryReturnValue, meta, arg) {
return baseQueryReturnValue
}
若要變更,請提供一個看起來像這樣的函式
transformResponse: (response, meta, arg) =>
response.some.deeply.nested.collection
transformResponse
會使用 baseQuery
回傳的 meta
屬性作為其第二個參數呼叫,可用於在決定轉換後的回應時使用。meta
的值取決於所使用的 baseQuery
。
transformResponse: (response: { sideA: Tracks; sideB: Tracks }, meta, arg) => {
if (meta?.coinFlip === 'heads') {
return response.sideA
}
return response.sideB
}
transformResponse
會使用提供給端點的 arg
屬性作為其第三個參數呼叫,可用於在決定轉換後的回應時使用。arg
的值取決於所使用的 endpoint
,以及在呼叫查詢/突變時使用的參數。
transformResponse: (response: Posts, meta, arg) => {
return {
originalArg: arg,
data: response,
}
}
雖然在 RTK Query 管理快取資料的情況下,較不需要將回應儲存在 正規化查詢表 中,但如果需要,可以使用 transformResponse
來執行此操作。
transformResponse: (response) =>
response.reduce((acc, curr) => {
acc[curr.id] = curr
return acc
}, {})
/*
will convert:
[
{id: 1, name: 'Harry'},
{id: 2, name: 'Ron'},
{id: 3, name: 'Hermione'},
]
to:
{
1: { id: 1, name: "Harry" },
2: { id: 2, name: "Ron" },
3: { id: 3, name: "Hermione" },
}
*/
createEntityAdapter
也可以與 transformResponse
一起使用來正規化資料,同時也能利用 createEntityAdapter
提供的其他功能,包括提供 ids
陣列、使用 sortComparer
來維護一致排序的清單,以及維持強大的 TypeScript 支援。
另請參閱 Websocket Chat API 與轉換後的回應形狀,了解 transformResponse
正規化回應資料與 createEntityAdapter
結合使用的範例,同時也使用 串流更新
來更新更多資料。
使用 transformErrorResponse
自訂查詢回應
createApi
上的個別端點接受 transformErrorResponse
屬性,可在查詢或變異傳回的錯誤送至快取前對其進行處理。
transformErrorResponse
會呼叫失敗的 baseQuery
為對應端點傳回的錯誤,而 transformErrorResponse
的傳回值會用作與該端點呼叫相關聯的快取錯誤。
預設情況下,伺服器上的酬載會直接回傳。
- TypeScript
- JavaScript
function defaultTransformResponse(
baseQueryReturnValue: unknown,
meta: unknown,
arg: unknown
) {
return baseQueryReturnValue
}
function defaultTransformResponse(baseQueryReturnValue, meta, arg) {
return baseQueryReturnValue
}
若要變更,請提供一個看起來像這樣的函式
transformErrorResponse: (response, meta, arg) =>
response.data.some.deeply.nested.errorObject
transformErrorResponse
會呼叫 baseQuery
傳回的 meta
屬性作為其第二個引數,可在決定轉換後的回應時使用。meta
的值取決於所使用的 baseQuery
。
transformErrorResponse: (
response: { data: { sideA: Tracks; sideB: Tracks } },
meta,
arg,
) => {
if (meta?.coinFlip === 'heads') {
return response.data.sideA
}
return response.data.sideB
}
transformErrorResponse
會呼叫提供給端點的 arg
屬性作為其第三個引數,可在決定轉換後的回應時使用。arg
的值取決於所使用的 endpoint
,以及呼叫查詢/變異時所使用的引數。
transformErrorResponse: (response: Posts, meta, arg) => {
return {
originalArg: arg,
error: response,
}
}
使用 queryFn
自訂查詢
RTK Query 內建 fetchBaseQuery
,可輕鬆定義與 HTTP URL(例如一般 REST API)通訊的端點。我們也整合了 GraphQL。不過,RTK Query 的核心在於追蹤載入狀態和快取值,適用於任何非同步請求/回應序列,不限於 HTTP 請求。
RTK Query 支援定義執行任意非同步邏輯並傳回結果的端點。createApi
上的個別端點接受 queryFn
屬性,讓您可以撰寫自己的非同步函式,並在其中放入任何想要的邏輯。
這在以下情況下很有用,也就是您希望對單一端點有特別不同的行為,或查詢本身不相關,包括
- 使用不同基本 URL 的一次性查詢
- 使用不同請求處理方式(例如自動重試)的一次性查詢
- 使用不同錯誤處理行為的一次性查詢
- 使用第三方程式庫 SDK(例如 Firebase 或 Supabase)提出請求的查詢
- 執行非典型請求/回應的非同步任務的查詢
- 使用單一查詢執行多個請求(範例)
- 利用無相關查詢的失效行為(範例)
- 在沒有相關初始請求的情況下使用串流更新(範例)
另請參閱queryFn API 參考
以取得類型簽章和可用的選項。
實作 queryFn
queryFn
可視為內嵌的 baseQuery
。它將使用與 baseQuery
相同的引數呼叫,以及提供的 baseQuery
函式本身(arg
、api
、extraOptions
和 baseQuery
)。與 baseQuery
類似,它預期會傳回具有 data
或 error
屬性的物件,或會解析為傳回此類物件的承諾。
基本 queryFn
範例
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { userAPI, User } from './userAPI'
const api = createApi({
baseQuery: fetchBaseQuery({ url: '/' }),
endpoints: (build) => ({
// normal HTTP endpoint using fetchBaseQuery
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
}),
// endpoint with a custom `queryFn` and separate async logic
getUser: build.query<User, string>({
queryFn: async (userId: string) => {
try {
const user = await userApi.getUserById(userId)
// Return the result in an object with a `data` field
return { data: user }
} catch (error) {
// Catch any errors and return them as an object with an `error` field
return { error }
}
},
}),
}),
})
queryFn 函式引數
const queryFn = (
args,
{ signal, dispatch, getState },
extraOptions,
baseQuery,
) => {
// omitted
}
queryFn 函式傳回值
- 預期的成功結果格式
return { data: YourData }
- 預期的錯誤結果格式
return { error: YourError }
const queryFn = (
args,
{ signal, dispatch, getState },
extraOptions,
baseQuery,
) => {
if (Math.random() > 0.5) return { error: 'Too high!' }
return { data: 'All good!' }
}
範例 - baseQuery
Axios baseQuery
此範例實作非常基本的基於 axios 的 baseQuery
實用程式。
- TypeScript
- JavaScript
import { createApi } from '@reduxjs/toolkit/query'
import type { BaseQueryFn } from '@reduxjs/toolkit/query'
import axios from 'axios'
import type { AxiosRequestConfig, AxiosError } from 'axios'
const axiosBaseQuery =
(
{ baseUrl }: { baseUrl: string } = { baseUrl: '' }
): BaseQueryFn<
{
url: string
method?: AxiosRequestConfig['method']
data?: AxiosRequestConfig['data']
params?: AxiosRequestConfig['params']
headers?: AxiosRequestConfig['headers']
},
unknown,
unknown
> =>
async ({ url, method, data, params, headers }) => {
try {
const result = await axios({
url: baseUrl + url,
method,
data,
params,
headers,
})
return { data: result.data }
} catch (axiosError) {
const err = axiosError as AxiosError
return {
error: {
status: err.response?.status,
data: err.response?.data || err.message,
},
}
}
}
const api = createApi({
baseQuery: axiosBaseQuery({
baseUrl: 'https://example.com',
}),
endpoints(build) {
return {
query: build.query({ query: () => ({ url: '/query', method: 'get' }) }),
mutation: build.mutation({
query: () => ({ url: '/mutation', method: 'post' }),
}),
}
},
})
import { createApi } from '@reduxjs/toolkit/query'
import axios from 'axios'
const axiosBaseQuery =
({ baseUrl } = { baseUrl: '' }) =>
async ({ url, method, data, params, headers }) => {
try {
const result = await axios({
url: baseUrl + url,
method,
data,
params,
headers,
})
return { data: result.data }
} catch (axiosError) {
const err = axiosError
return {
error: {
status: err.response?.status,
data: err.response?.data || err.message,
},
}
}
}
const api = createApi({
baseQuery: axiosBaseQuery({
baseUrl: 'https://example.com',
}),
endpoints(build) {
return {
query: build.query({ query: () => ({ url: '/query', method: 'get' }) }),
mutation: build.mutation({
query: () => ({ url: '/mutation', method: 'post' }),
}),
}
},
})
GraphQL baseQuery
此範例實作非常基本的基於 GraphQL 的 baseQuery
。
- TypeScript
- JavaScript
import { createApi } from '@reduxjs/toolkit/query'
import { request, gql, ClientError } from 'graphql-request'
const graphqlBaseQuery =
({ baseUrl }: { baseUrl: string }) =>
async ({ body }: { body: string }) => {
try {
const result = await request(baseUrl, body)
return { data: result }
} catch (error) {
if (error instanceof ClientError) {
return { error: { status: error.response.status, data: error } }
}
return { error: { status: 500, data: error } }
}
}
export const api = createApi({
baseQuery: graphqlBaseQuery({
baseUrl: 'https://graphqlzero.almansi.me/api',
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => ({
body: gql`
query {
posts {
data {
id
title
}
}
}
`,
}),
transformResponse: (response) => response.posts.data,
}),
getPost: builder.query({
query: (id) => ({
body: gql`
query {
post(id: ${id}) {
id
title
body
}
}
`,
}),
transformResponse: (response) => response.post,
}),
}),
})
import { createApi } from '@reduxjs/toolkit/query'
import { request, gql, ClientError } from 'graphql-request'
const graphqlBaseQuery =
({ baseUrl }) =>
async ({ body }) => {
try {
const result = await request(baseUrl, body)
return { data: result }
} catch (error) {
if (error instanceof ClientError) {
return { error: { status: error.response.status, data: error } }
}
return { error: { status: 500, data: error } }
}
}
export const api = createApi({
baseQuery: graphqlBaseQuery({
baseUrl: 'https://graphqlzero.almansi.me/api',
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => ({
body: gql`
query {
posts {
data {
id
title
}
}
}
`,
}),
transformResponse: (response) => response.posts.data,
}),
getPost: builder.query({
query: (id) => ({
body: gql`
query {
post(id: ${id}) {
id
title
body
}
}
`,
}),
transformResponse: (response) => response.post,
}),
}),
})
透過延伸 fetchBaseQuery 自動重新授權
此範例封裝 fetchBaseQuery
,如此一來,當遇到 401 未授權
錯誤時,會傳送額外的要求嘗試更新授權令牌,並在重新授權後重新嘗試初始查詢。
- TypeScript
- JavaScript
import { fetchBaseQuery } from '@reduxjs/toolkit/query'
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import { tokenReceived, loggedOut } from './authSlice'
const baseQuery = fetchBaseQuery({ baseUrl: '/' })
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
let result = await baseQuery(args, api, extraOptions)
if (result.error && result.error.status === 401) {
// try to get a new token
const refreshResult = await baseQuery('/refreshToken', api, extraOptions)
if (refreshResult.data) {
// store the new token
api.dispatch(tokenReceived(refreshResult.data))
// retry the initial query
result = await baseQuery(args, api, extraOptions)
} else {
api.dispatch(loggedOut())
}
}
return result
}
import { fetchBaseQuery } from '@reduxjs/toolkit/query'
import { tokenReceived, loggedOut } from './authSlice'
const baseQuery = fetchBaseQuery({ baseUrl: '/' })
const baseQueryWithReauth = async (args, api, extraOptions) => {
let result = await baseQuery(args, api, extraOptions)
if (result.error && result.error.status === 401) {
// try to get a new token
const refreshResult = await baseQuery('/refreshToken', api, extraOptions)
if (refreshResult.data) {
// store the new token
api.dispatch(tokenReceived(refreshResult.data))
// retry the initial query
result = await baseQuery(args, api, extraOptions)
} else {
api.dispatch(loggedOut())
}
}
return result
}
防止多個未授權錯誤
使用 async-mutex
防止當多個呼叫因 401 未授權
錯誤而失敗時,多個呼叫至 '/refreshToken'。
- TypeScript
- JavaScript
import { fetchBaseQuery } from '@reduxjs/toolkit/query'
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import { tokenReceived, loggedOut } from './authSlice'
import { Mutex } from 'async-mutex'
// create a new mutex
const mutex = new Mutex()
const baseQuery = fetchBaseQuery({ baseUrl: '/' })
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
// wait until the mutex is available without locking it
await mutex.waitForUnlock()
let result = await baseQuery(args, api, extraOptions)
if (result.error && result.error.status === 401) {
// checking whether the mutex is locked
if (!mutex.isLocked()) {
const release = await mutex.acquire()
try {
const refreshResult = await baseQuery(
'/refreshToken',
api,
extraOptions
)
if (refreshResult.data) {
api.dispatch(tokenReceived(refreshResult.data))
// retry the initial query
result = await baseQuery(args, api, extraOptions)
} else {
api.dispatch(loggedOut())
}
} finally {
// release must be called once the mutex should be released again.
release()
}
} else {
// wait until the mutex is available without locking it
await mutex.waitForUnlock()
result = await baseQuery(args, api, extraOptions)
}
}
return result
}
import { fetchBaseQuery } from '@reduxjs/toolkit/query'
import { tokenReceived, loggedOut } from './authSlice'
import { Mutex } from 'async-mutex'
// create a new mutex
const mutex = new Mutex()
const baseQuery = fetchBaseQuery({ baseUrl: '/' })
const baseQueryWithReauth = async (args, api, extraOptions) => {
// wait until the mutex is available without locking it
await mutex.waitForUnlock()
let result = await baseQuery(args, api, extraOptions)
if (result.error && result.error.status === 401) {
// checking whether the mutex is locked
if (!mutex.isLocked()) {
const release = await mutex.acquire()
try {
const refreshResult = await baseQuery(
'/refreshToken',
api,
extraOptions
)
if (refreshResult.data) {
api.dispatch(tokenReceived(refreshResult.data))
// retry the initial query
result = await baseQuery(args, api, extraOptions)
} else {
api.dispatch(loggedOut())
}
} finally {
// release must be called once the mutex should be released again.
release()
}
} else {
// wait until the mutex is available without locking it
await mutex.waitForUnlock()
result = await baseQuery(args, api, extraOptions)
}
}
return result
}
自動重試
RTK Query 匯出一個名為 retry
的公用程式,您可以用它來封裝 API 定義中的 baseQuery
。它預設嘗試 5 次,採用基本指數退避。
預設行為會在下列間隔重試
- 600ms * random(0.4, 1.4)
- 1200ms * random(0.4, 1.4)
- 2400ms * random(0.4, 1.4)
- 4800ms * random(0.4, 1.4)
- 9600ms * random(0.4, 1.4)
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react'
interface Post {
id: number
name: string
}
type PostsResponse = Post[]
// maxRetries: 5 is the default, and can be omitted. Shown for documentation purposes.
const staggeredBaseQuery = retry(fetchBaseQuery({ baseUrl: '/' }), {
maxRetries: 5,
})
export const api = createApi({
baseQuery: staggeredBaseQuery,
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
}),
getPost: build.query<PostsResponse, string>({
query: (id) => ({ url: `post/${id}` }),
extraOptions: { maxRetries: 8 }, // You can override the retry behavior on each endpoint
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery } = api
import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react'
// maxRetries: 5 is the default, and can be omitted. Shown for documentation purposes.
const staggeredBaseQuery = retry(fetchBaseQuery({ baseUrl: '/' }), {
maxRetries: 5,
})
export const api = createApi({
baseQuery: staggeredBaseQuery,
endpoints: (build) => ({
getPosts: build.query({
query: () => ({ url: 'posts' }),
}),
getPost: build.query({
query: (id) => ({ url: `post/${id}` }),
extraOptions: { maxRetries: 8 }, // You can override the retry behavior on each endpoint
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery } = api
如果您不想在特定端點上重試,您可以設定 maxRetries: 0
。
掛鉤有可能同時傳回 資料
和 錯誤
。預設情況下,RTK Query 會保留 資料
中最後一個「良好」的結果,直到它可以更新或被垃圾回收為止。
放棄錯誤重試
retry
公用程式有一個 fail
方法屬性,可用於立即放棄重試。這可以用於已知額外的重試必定全部失敗且多餘的情況。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react'
import type { FetchArgs } from '@reduxjs/toolkit/query'
interface Post {
id: number
name: string
}
type PostsResponse = Post[]
const staggeredBaseQueryWithBailOut = retry(
async (args: string | FetchArgs, api, extraOptions) => {
const result = await fetchBaseQuery({ baseUrl: '/api/' })(
args,
api,
extraOptions
)
// bail out of re-tries immediately if unauthorized,
// because we know successive re-retries would be redundant
if (result.error?.status === 401) {
retry.fail(result.error)
}
return result
},
{
maxRetries: 5,
}
)
export const api = createApi({
baseQuery: staggeredBaseQueryWithBailOut,
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
}),
getPost: build.query<Post, string>({
query: (id) => ({ url: `post/${id}` }),
extraOptions: { maxRetries: 8 }, // You can override the retry behavior on each endpoint
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery } = api
import { createApi, fetchBaseQuery, retry } from '@reduxjs/toolkit/query/react'
const staggeredBaseQueryWithBailOut = retry(
async (args, api, extraOptions) => {
const result = await fetchBaseQuery({ baseUrl: '/api/' })(
args,
api,
extraOptions
)
// bail out of re-tries immediately if unauthorized,
// because we know successive re-retries would be redundant
if (result.error?.status === 401) {
retry.fail(result.error)
}
return result
},
{
maxRetries: 5,
}
)
export const api = createApi({
baseQuery: staggeredBaseQueryWithBailOut,
endpoints: (build) => ({
getPosts: build.query({
query: () => ({ url: 'posts' }),
}),
getPost: build.query({
query: (id) => ({ url: `post/${id}` }),
extraOptions: { maxRetries: 8 }, // You can override the retry behavior on each endpoint
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery } = api
將元資訊新增至查詢
baseQuery
也可以在其傳回值中包含一個 meta
屬性。在某些情況下,這可能很有用,例如您可能希望包含與要求相關的額外資訊,例如要求 ID 或時間戳記。
在這種情況下,傳回值看起來會像這樣
- 預期成功結果格式(含 meta 資料)
return { data: YourData, meta: YourMeta }
- 預期錯誤結果格式(含 meta 資料)
return { error: YourError, meta: YourMeta }
- TypeScript
- JavaScript
import { fetchBaseQuery, createApi } from '@reduxjs/toolkit/query'
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query'
import type { FetchBaseQueryMeta } from '@reduxjs/toolkit/dist/query/fetchBaseQuery'
import { uuid } from './idGenerator'
type Meta = {
requestId: string
timestamp: number
}
const metaBaseQuery: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError,
{},
Meta & FetchBaseQueryMeta
> = async (args, api, extraOptions) => {
const requestId = uuid()
const timestamp = Date.now()
const baseResult = await fetchBaseQuery({ baseUrl: '/' })(
args,
api,
extraOptions
)
return {
...baseResult,
meta: baseResult.meta && { ...baseResult.meta, requestId, timestamp },
}
}
const DAY_MS = 24 * 60 * 60 * 1000
interface Post {
id: number
name: string
timestamp: number
}
type PostsResponse = Post[]
const api = createApi({
baseQuery: metaBaseQuery,
endpoints: (build) => ({
// a theoretical endpoint where we only want to return data
// if request was performed past a certain date
getRecentPosts: build.query<PostsResponse, void>({
query: () => 'posts',
transformResponse: (returnValue: PostsResponse, meta) => {
// `meta` here contains our added `requestId` & `timestamp`, as well as
// `request` & `response` from fetchBaseQuery's meta object.
// These properties can be used to transform the response as desired.
if (!meta) return []
return returnValue.filter(
(post) => post.timestamp >= meta.timestamp - DAY_MS
)
},
}),
}),
})
import { fetchBaseQuery, createApi } from '@reduxjs/toolkit/query'
import { uuid } from './idGenerator'
const metaBaseQuery = async (args, api, extraOptions) => {
const requestId = uuid()
const timestamp = Date.now()
const baseResult = await fetchBaseQuery({ baseUrl: '/' })(
args,
api,
extraOptions
)
return {
...baseResult,
meta: baseResult.meta && { ...baseResult.meta, requestId, timestamp },
}
}
const DAY_MS = 24 * 60 * 60 * 1000
const api = createApi({
baseQuery: metaBaseQuery,
endpoints: (build) => ({
// a theoretical endpoint where we only want to return data
// if request was performed past a certain date
getRecentPosts: build.query({
query: () => 'posts',
transformResponse: (returnValue, meta) => {
// `meta` here contains our added `requestId` & `timestamp`, as well as
// `request` & `response` from fetchBaseQuery's meta object.
// These properties can be used to transform the response as desired.
if (!meta) return []
return returnValue.filter(
(post) => post.timestamp >= meta.timestamp - DAY_MS
)
},
}),
}),
})
使用 Redux 狀態建構動態基本網址
在某些情況下,您可能希望根據 Redux 狀態中的屬性來動態變更基本網址。baseQuery
可以存取 getState
方法,該方法會在呼叫時提供目前的儲存體狀態。這可以用於使用部分網址字串和儲存體狀態中的適當資料來建構所需的網址。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type {
BaseQueryFn,
FetchArgs,
FetchBaseQueryError,
} from '@reduxjs/toolkit/query/react'
import type { Post } from './types'
import { selectProjectId } from './projectSlice'
import type { RootState } from '../store'
const rawBaseQuery = fetchBaseQuery({
baseUrl: 'www.my-cool-site.com/',
})
const dynamicBaseQuery: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
const projectId = selectProjectId(api.getState() as RootState)
// gracefully handle scenarios where data to generate the URL is missing
if (!projectId) {
return {
error: {
status: 400,
statusText: 'Bad Request',
data: 'No project ID received',
},
}
}
const urlEnd = typeof args === 'string' ? args : args.url
// construct a dynamically generated portion of the url
const adjustedUrl = `project/${projectId}/${urlEnd}`
const adjustedArgs =
typeof args === 'string' ? adjustedUrl : { ...args, url: adjustedUrl }
// provide the amended url and other params to the raw base query
return rawBaseQuery(adjustedArgs, api, extraOptions)
}
export const api = createApi({
baseQuery: dynamicBaseQuery,
endpoints: (builder) => ({
getPosts: builder.query<Post[], void>({
query: () => 'posts',
}),
}),
})
export const { useGetPostsQuery } = api
/*
Using `useGetPostsQuery()` where a `projectId` of 500 is in the redux state will result in
a request being sent to www.my-cool-site.com/project/500/posts
*/
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { selectProjectId } from './projectSlice'
const rawBaseQuery = fetchBaseQuery({
baseUrl: 'www.my-cool-site.com/',
})
const dynamicBaseQuery = async (args, api, extraOptions) => {
const projectId = selectProjectId(api.getState())
// gracefully handle scenarios where data to generate the URL is missing
if (!projectId) {
return {
error: {
status: 400,
statusText: 'Bad Request',
data: 'No project ID received',
},
}
}
const urlEnd = typeof args === 'string' ? args : args.url
// construct a dynamically generated portion of the url
const adjustedUrl = `project/${projectId}/${urlEnd}`
const adjustedArgs =
typeof args === 'string' ? adjustedUrl : { ...args, url: adjustedUrl }
// provide the amended url and other params to the raw base query
return rawBaseQuery(adjustedArgs, api, extraOptions)
}
export const api = createApi({
baseQuery: dynamicBaseQuery,
endpoints: (builder) => ({
getPosts: builder.query({
query: () => 'posts',
}),
}),
})
export const { useGetPostsQuery } = api
/*
Using `useGetPostsQuery()` where a `projectId` of 500 is in the redux state will result in
a request being sent to www.my-cool-site.com/project/500/posts
*/
範例 - transformResponse
解開深度巢狀的 GraphQL 資料
- TypeScript
- JavaScript
import { createApi } from '@reduxjs/toolkit/query'
import { graphqlBaseQuery, gql } from './graphqlBaseQuery'
interface Post {
id: number
title: string
}
export const api = createApi({
baseQuery: graphqlBaseQuery({
baseUrl: '/graphql',
}),
endpoints: (builder) => ({
getPosts: builder.query<Post[], void>({
query: () => ({
body: gql`
query {
posts {
data {
id
title
}
}
}
`,
}),
transformResponse: (response: { posts: { data: Post[] } }) =>
response.posts.data,
}),
}),
})
import { createApi } from '@reduxjs/toolkit/query'
import { graphqlBaseQuery, gql } from './graphqlBaseQuery'
export const api = createApi({
baseQuery: graphqlBaseQuery({
baseUrl: '/graphql',
}),
endpoints: (builder) => ({
getPosts: builder.query({
query: () => ({
body: gql`
query {
posts {
data {
id
title
}
}
}
`,
}),
transformResponse: (response) => response.posts.data,
}),
}),
})
使用 createEntityAdapter
正規化資料
在以下範例中,transformResponse
與 createEntityAdapter
結合使用,在將資料儲存在快取中之前先正規化資料。
對於下列回應
[
{ id: 1, name: 'Harry' },
{ id: 2, name: 'Ron' },
{ id: 3, name: 'Hermione' },
]
正規化的快取資料將儲存為
{
ids: [1, 3, 2],
entities: {
1: { id: 1, name: "Harry" },
2: { id: 2, name: "Ron" },
3: { id: 3, name: "Hermione" },
}
}
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { createEntityAdapter } from '@reduxjs/toolkit'
import type { EntityState } from '@reduxjs/toolkit'
export interface Post {
id: number
name: string
}
const postsAdapter = createEntityAdapter<Post>({
sortComparer: (a, b) => a.name.localeCompare(b.name),
})
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPosts: build.query<EntityState<Post, number>, void>({
query: () => `posts`,
transformResponse(response: Post[]) {
return postsAdapter.addMany(postsAdapter.getInitialState(), response)
},
}),
}),
})
export const { useGetPostsQuery } = api
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { createEntityAdapter } from '@reduxjs/toolkit'
const postsAdapter = createEntityAdapter({
sortComparer: (a, b) => a.name.localeCompare(b.name),
})
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPosts: build.query({
query: () => `posts`,
transformResponse(response) {
return postsAdapter.addMany(postsAdapter.getInitialState(), response)
},
}),
}),
})
export const { useGetPostsQuery } = api
範例 - queryFn
使用第三方 SDK
許多服務(例如 Firebase 和 Supabase)提供自己的 SDK 來發送請求。您可以在 queryFn
中使用這些 SDK 方法
import { createApi, fakeBaseQuery } from '@reduxjs/toolkit/query/react'
import { supabase } from './supabaseApi'
export const supabaseApi = createApi({
reducerPath: 'supabaseApi',
baseQuery: fakeBaseQuery(),
endpoints: (builder) => ({
getBlogs: builder.query({
queryFn: async () => {
// Supabase conveniently already has `data` and `error` fields
const { data, error } = await supabase.from('blogs').select()
if (error) {
return { error }
}
return { data }
},
}),
}),
})
您也可以嘗試建立一個使用 SDK 的自訂基本查詢,並定義將方法名稱或參數傳遞到該基本查詢的端點。
使用 no-op queryFn
在某些情況下,您可能希望有一個 query
或 mutation
,其中傳送請求或傳回資料與情況無關。此類情況將利用 invalidatesTags
屬性來強制重新擷取已提供給快取的特定 tags
。
請參閱 提供錯誤給快取
,以查看此類情況的更多詳細資訊和範例,以「重新擷取有錯誤的查詢」。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query<Post[], void>({
query: () => 'posts',
providesTags: ['Post'],
}),
getUsers: build.query<User[], void>({
query: () => 'users',
providesTags: ['User'],
}),
refetchPostsAndUsers: build.mutation<null, void>({
// The query is not relevant here, so a `null` returning `queryFn` is used
queryFn: () => ({ data: null }),
// This mutation takes advantage of tag invalidation behaviour to trigger
// any queries that provide the 'Post' or 'User' tags to re-fetch if the queries
// are currently subscribed to the cached data
invalidatesTags: ['Post', 'User'],
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
getPosts: build.query({
query: () => 'posts',
providesTags: ['Post'],
}),
getUsers: build.query({
query: () => 'users',
providesTags: ['User'],
}),
refetchPostsAndUsers: build.mutation({
// The query is not relevant here, so a `null` returning `queryFn` is used
queryFn: () => ({ data: null }),
// This mutation takes advantage of tag invalidation behaviour to trigger
// any queries that provide the 'Post' or 'User' tags to re-fetch if the queries
// are currently subscribed to the cached data
invalidatesTags: ['Post', 'User'],
}),
}),
})
無初始要求的串流資料
RTK Query 提供終端點的功能,可傳送資料的初始要求,然後再進行重複的串流更新,在更新發生時對快取資料執行進一步的更新。不過,初始要求是可選的,而且你可能希望在沒有觸發任何初始要求的情況下使用串流更新。
在以下範例中,queryFn
用於使用空陣列填入快取資料,且未傳送初始要求。稍後會透過onCacheEntryAdded
終端點選項使用串流更新來填入陣列,並在收到時更新快取資料。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { Message } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Message'],
endpoints: (build) => ({
streamMessages: build.query<Message[], void>({
// The query is not relevant here as the data will be provided via streaming updates.
// A queryFn returning an empty array is used, with contents being populated via
// streaming updates below as they are received.
queryFn: () => ({ data: [] }),
async onCacheEntryAdded(arg, { updateCachedData, cacheEntryRemoved }) {
const ws = new WebSocket('ws://127.0.0.1:8080')
// populate the array with messages as they are received from the websocket
ws.addEventListener('message', (event) => {
updateCachedData((draft) => {
draft.push(JSON.parse(event.data))
})
})
await cacheEntryRemoved
ws.close()
},
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Message'],
endpoints: (build) => ({
streamMessages: build.query({
// The query is not relevant here as the data will be provided via streaming updates.
// A queryFn returning an empty array is used, with contents being populated via
// streaming updates below as they are received.
queryFn: () => ({ data: [] }),
async onCacheEntryAdded(arg, { updateCachedData, cacheEntryRemoved }) {
const ws = new WebSocket('ws://127.0.0.1:8080')
// populate the array with messages as they are received from the websocket
ws.addEventListener('message', (event) => {
updateCachedData((draft) => {
draft.push(JSON.parse(event.data))
})
})
await cacheEntryRemoved
ws.close()
},
}),
}),
})
使用單一查詢執行多個要求
在以下範例中,撰寫查詢以擷取隨機使用者的所有貼文。這是透過針對隨機使用者進行第一次要求,然後取得該使用者的所有貼文來完成的。使用 queryFn
可將兩個要求包含在單一查詢中,避免在元件程式碼中串連該邏輯。
- TypeScript
- JavaScript
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
import type { Post, User } from './types'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/ ' }),
endpoints: (build) => ({
getRandomUserPosts: build.query<Post, void>({
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
// get a random user
const randomResult = await fetchWithBQ('users/random')
if (randomResult.error)
return { error: randomResult.error as FetchBaseQueryError }
const user = randomResult.data as User
const result = await fetchWithBQ(`user/${user.id}/posts`)
return result.data
? { data: result.data as Post }
: { error: result.error as FetchBaseQueryError }
},
}),
}),
})
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/ ' }),
endpoints: (build) => ({
getRandomUserPosts: build.query({
async queryFn(_arg, _queryApi, _extraOptions, fetchWithBQ) {
// get a random user
const randomResult = await fetchWithBQ('users/random')
if (randomResult.error) return { error: randomResult.error }
const user = randomResult.data
const result = await fetchWithBQ(`user/${user.id}/posts`)
return result.data ? { data: result.data } : { error: result.error }
},
}),
}),
})