自定义查询
RTK Query 对于你的请求如何解析并不关心。你可以使用任何你喜欢的库来处理请求,或者完全不使用库。RTK Query 提供了预期能覆盖大多数用例的合理默认值,同时也允许自定义以改变查询处理以适应特定需求。
使用 baseQuery
自定义查询
处理查询的默认方法是通过 createApi
的 baseQuery
选项,结合端点定义的 query
选项。
为了处理查询,端点被定义为一个 query
选项,它将其返回值传递给用于 API 的通用 baseQuery
函数。
默认情况下,RTK Query 附带了 fetchBaseQuery
,这是一个轻量级的 fetch
包装器,它自动处理请求头和响应解析,类似于常见的库如 axios
。如果 fetchBaseQuery
本身不能满足你的需求,你可以使用一个包装函数来自定义其行为,或者为 createApi
使用从头开始创建你自己的 baseQuery
函数。
实现一个自定义的 baseQuery
RTK Query 期望 baseQuery
函数被调用时带有三个参数:args
,api
和 extraOptions
。它应该返回一个带有 data
或 error
属性的对象,或者一个解析为返回此类对象的 promise。
基础查询和查询函数必须 始终 自己捕获 错误,并在对象中返回它!
function brokenCustomBaseQuery() {
// ❌ 不要让这个自己抛出
const data = await fetchSomeData()
return { data }
}
function correctCustomBaseQuery() {
// ✅ 捕获错误并 _返回_ 它们,以便 RTKQ 逻辑可以跟踪它
try {
const data = await fetchSomeData()
return { data }
} catch (error) {
return { error }
}
}
baseQuery 函数参数
const customBaseQuery = (
args,
{ signal, dispatch, getState },
extraOptions,
) => {
// 省略
}
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
的返回值将被用作与该端点调用相关联的缓存数据。
默认情况下,服务器的有效载荷将直接返回。
function defaultTransformResponse(
baseQueryReturnValue: unknown,
meta: unknown,
arg: unknown,
) {
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
}, {})
/*
将会转换:
[
{id: 1, name: 'Harry'},
{id: 2, name: 'Ron'},
{id: 3, name: 'Hermione'},
]
为:
{
1: { id: 1, name: "Harry" },
2: { id: 2, name: "Ron" },
3: { id: 3, name: "Hermione" },
}
*/
createEntityAdapter
也可以与 transformResponse
一起使用来标准化数据,同时也可以利用 createEntityAdapter
提供的其他功能,包括提供一个 ids
数组,使用 sortComparer
来维护一个始终排序的列表,以及维护强大的 TypeScript 支持。
另请参见 具有转换响应形状的 Websocket 聊天 API,该示例展示了 transformResponse
如何与 createEntityAdapter
结合使用来标准化响应数据,同时还使用 streaming updates
更新更多数据。
使用 transformErrorResponse
自定义查询响应
createApi
上的单个端点接受一个transformErrorResponse
属性,该属性允许在查询或突变返回错误并命中缓存之前对其进行操作。
transformErrorResponse
会在失败的 baseQuery
为相应端点返回错误时被调用,transformErrorResponse
的返回值将被用作与该端点调用相关联的缓存错误。
默认情况下,服务器的有效载荷将直接返回。
function defaultTransformResponse(
baseQueryReturnValue: unknown,
meta: unknown,
arg: unknown,
) {
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 Reference
了解类型签名和可用选项。
实现一个 queryFn
queryFn
可以被视为一个内联的 baseQuery
。它将被调用与 baseQuery
相同的参数,以及提供的 baseQuery
函数本身(arg
,api
,extraOptions
和 baseQuery
)。类似于 baseQuery
,它预期返回一个具有 data
或 error
属性的对象,或者一个解析为返回此类对象的 promise。
基础 queryFn
示例
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
import { userAPI, User } from './userAPI'
const api = createApi({
baseQuery: fetchBaseQuery({ url: '/' }),
endpoints: (build) => ({
// 使用 fetchBaseQuery 的正常 HTTP 端点
getPosts: build.query<PostsResponse, void>({
query: () => ({ url: 'posts' }),
}),
// 具有自定义 `queryFn` 和单独异步逻辑的端点
getUser: build.query<User, string>({
queryFn: async (userId: string) => {
try {
const user = await userApi.getUserById(userId)
// 在一个带有 `data` 字段的对象中返回结果
return { data: user }
} catch (error) {
// 捕获任何错误并将它们作为带有 `error` 字段的对象返回
return { error }
}
},
}),
}),
})
queryFn 函数参数
const queryFn = (
args,
{ signal, dispatch, getState },
extraOptions,
baseQuery,
) => {
// 省略
}
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
工具。
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' }),
}),
}
},
})
GraphQL baseQuery
这个例子实现了一个非常基础的基于GraphQL的baseQuery
。
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,
}),
}),
})