跳到主要内容

 

自定义查询

RTK Query 对于你的请求如何解析并不关心。你可以使用任何你喜欢的库来处理请求,或者完全不使用库。RTK Query 提供了预期能覆盖大多数用例的合理默认值,同时也允许自定义以改变查询处理以适应特定需求。

使用 baseQuery 自定义查询

处理查询的默认方法是通过 createApibaseQuery 选项,结合端点定义的 query 选项。

为了处理查询,端点被定义为一个 query 选项,它将其返回值传递给用于 API 的通用 baseQuery 函数。

默认情况下,RTK Query 附带了 fetchBaseQuery,这是一个轻量级的 fetch 包装器,它自动处理请求头和响应解析,类似于常见的库如 axios。如果 fetchBaseQuery 本身不能满足你的需求,你可以使用一个包装函数来自定义其行为,或者为 createApi 使用从头开始创建你自己的 baseQuery 函数。

参见 baseQuery API Reference

实现一个自定义的 baseQuery

RTK Query 期望 baseQuery 函数被调用时带有三个参数:argsapiextraOptions。它应该返回一个带有 dataerror 属性的对象,或者一个解析为返回此类对象的 promise。

提示

基础查询和查询函数必须 始终 自己捕获错误,并在对象中返回它!

function brokenCustomBaseQuery() {
// ❌ 不要让这个自己抛出
const data = await fetchSomeData()
return { data }
}

function correctCustomBaseQuery() {
// ✅ 捕获错误并 _返回_ 它们,以便 RTKQ 逻辑可以跟踪它
try {
const data = await fetchSomeData()
return { data }
} catch (error) {
return { error }
}
}

baseQuery 函数参数

baseQuery 示例参数
const customBaseQuery = (
args,
{ signal, dispatch, getState },
extraOptions,
) => {
// 省略
}

baseQuery 函数返回值

  1. 预期的成功结果格式
    return { data: YourData }
  2. 预期的错误结果格式
    return { error: YourError }
baseQuery 示例返回值
const customBaseQuery = (
args,
{ signal, dispatch, getState },
extraOptions,
) => {
if (Math.random() > 0.5) return { error: 'Too high!' }
return { data: 'All good!' }
}
备注

这种格式是必需的,以便 RTK Query 可以推断你的响应的返回类型。

在其核心,一个 baseQuery 函数只需要有最小的返回值才能有效;一个带有 dataerror 属性的对象。由用户决定如何使用提供的参数,以及在函数内部如何处理请求。

fetchBaseQuery 默认值

对于 fetchBaseQuery 特别地,返回类型如下:

fetchBaseQuery 的返回类型
Promise<
| {
data: any
error?: undefined
meta?: { request: Request; response: Response }
}
| {
error: {
status: number
data: any
}
data?: undefined
meta?: { request: Request; response: Response }
}
>
  1. 使用 fetchBaseQuery 的预期成功结果格式
    return { data: YourData }
  2. 使用 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 meta 示例
transformResponse: (response: { sideA: Tracks; sideB: Tracks }, meta, arg) => {
if (meta?.coinFlip === 'heads') {
return response.sideA
}
return response.sideB
}

transformResponse 会在 arg 属性提供给端点作为其第三个参数被调用,这可以在确定转换响应时使用。arg 的值取决于使用的 endpoint,以及调用查询/突变时使用的参数。

transformResponse arg 示例
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 meta 示例
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 arg 示例
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 函数本身(argapiextraOptionsbaseQuery)。类似于 baseQuery,它预期返回一个具有 dataerror 属性的对象,或者一个解析为返回此类对象的 promise。

基础 queryFn 示例

基础 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 函数参数

queryFn 示例参数
const queryFn = (
args,
{ signal, dispatch, getState },
extraOptions,
baseQuery,
) => {
// 省略
}

queryFn 函数返回值

  1. 预期的成功结果格式
    return { data: YourData }
  2. 预期的错误结果格式
    return { error: YourError }
queryFn 示例返回值
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 工具。

基础 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

基础的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,
}),
}),
})

通过扩展fetchBaseQuery自动重新授权

这个例子包装了fetchBaseQuery,当遇到401 Unauthorized错误时,会发送额外的请求尝试刷新授权令牌,并在重新授权后重试初始查询。

使用自定义base query模拟axios-like拦截器
// 文件: authSlice.ts noEmit
declare function tokenReceived(args?: any): void
declare function loggedOut(): void
export { tokenReceived, loggedOut }
// 文件: baseQueryWithReauth.ts
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) {
// 尝试获取新的令牌
const refreshResult = await baseQuery('/refreshToken', api, extraOptions)
if (refreshResult.data) {
// 存储新的令牌
api.dispatch(tokenReceived(refreshResult.data))
// 重试初始查询
result = await baseQuery(args, api, extraOptions)
} else {
api.dispatch(loggedOut())
}
}
return result
}

防止多个未授权错误

使用async-mutex防止当多个调用因401 Unauthorized错误失败时,多次调用'/refreshToken'。

防止多次调用'/refreshToken'
// 文件: authSlice.ts noEmit
declare function tokenReceived(args?: any): void
declare function loggedOut(): void
export { tokenReceived, loggedOut }
// 文件: baseQueryWithReauth.ts

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'

// 创建一个新的互斥锁
const mutex = new Mutex()
const baseQuery = fetchBaseQuery({ baseUrl: '/' })
const baseQueryWithReauth: BaseQueryFn<
string | FetchArgs,
unknown,
FetchBaseQueryError
> = async (args, api, extraOptions) => {
// 等待互斥锁可用,但不锁定它
await mutex.waitForUnlock()
let result = await baseQuery(args, api, extraOptions)
if (result.error && result.error.status === 401) {
// 检查互斥锁是否被锁定
if (!mutex.isLocked()) {
const release = await mutex.acquire()
try {
const refreshResult = await baseQuery(
'/refreshToken',
api,
extraOptions,
)
if (refreshResult.data) {
api.dispatch(tokenReceived(refreshResult.data))
// 重试初始查询
result = await baseQuery(args, api, extraOptions)
} else {
api.dispatch(loggedOut())
}
} finally {
// 一旦互斥锁应该再次被释放,必须调用release。
release()
}
} else {
// 等待互斥锁可用,但不锁定它
await mutex.waitForUnlock()
result = await baseQuery(args, api, extraOptions)
}
}
return result
}

自动重试

RTK Query 导出了一个名为 retry 的实用程序,你可以用它来包装 API 定义中的 baseQuery。它默认进行5次尝试,使用基本的指数退避。

默认行为将在以下间隔重试:

  1. 600ms * random(0.4, 1.4)
  2. 1200ms * random(0.4, 1.4)
  3. 2400ms * random(0.4, 1.4)
  4. 4800ms * random(0.4, 1.4)
  5. 9600ms * random(0.4, 1.4)
Retry every request 5 times by default
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;

如果你不想在特定的端点上重试,你可以设置 maxRetries: 0

信息

一个钩子可能同时返回 dataerror。默认情况下,RTK Query 会保留 data 中的最后一个'好'结果,直到它可以被更新或垃圾收集。

退出错误重试

retry 实用程序有一个 fail 方法属性,可以用来立即退出重试。这可以用于那些已知额外的重试肯定都会失败并且是多余的情况。

退出错误重试
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,
)

// 如果未授权,立即退出重试,
// 因为我们知道连续的重试将是多余的
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 }, // 你可以在每个端点上覆盖重试行为
}),
}),
})
export const { useGetPostsQuery, useGetPostQuery } = api

向查询添加元信息

baseQuery 的返回值也可以包含一个 meta 属性。在你可能希望包含与请求相关的额外信息的情况下,这可能是有益的,例如请求 ID 或时间戳。

在这种情况下,返回值将如下所示:

  1. 预期的成功结果格式带有元信息
    return { data: YourData, meta: YourMeta }
  2. 预期的错误结果格式带有元信息
    return { error: YourError, meta: YourMeta }
带有元信息的 baseQuery 示例
// 文件: idGenerator.ts noEmit
export declare const uuid: () => string

// 文件: metaBaseQuery.ts
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) => ({
// 一个理论上的端点,我们只想返回数据
// 如果请求是在某个日期之后执行的
getRecentPosts: build.query<PostsResponse, void>({
query: () => 'posts',
transformResponse: (returnValue: PostsResponse, meta) => {
// 这里的 `meta` 包含我们添加的 `requestId` 和 `timestamp`,以及
// fetchBaseQuery 的元对象的 `request` 和 `response`。
// 这些属性可以用来按需转换响应。
if (!meta) return []
return returnValue.filter(
(post) => post.timestamp >= meta.timestamp - DAY_MS,
)
},
}),
}),
})

使用 Redux 状态构造动态基础 URL

在某些情况下,你可能希望从 Redux 状态的属性中确定一个动态改变的基础 url。baseQuery 可以访问 getState 方法,该方法在被调用时提供当前的存储状态。这可以用来使用部分 url 字符串和存储状态中的适当数据来构造所需的 url。

动态生成基础 URL 示例
// 文件: src/store.ts noEmit
export type RootState = {
auth: {
projectId: number | null
}
}

// 文件: src/services/projectSlice.ts noEmit
import type { RootState } from '../store'
export const selectProjectId = (state: RootState) => state.auth.projectId

// 文件: src/services/types.ts noEmit
export interface Post {
id: number
name: string
}

// 文件: src/services/api.ts
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)
// 当生成 URL 的数据缺失时,优雅地处理情况
if (!projectId) {
return {
error: {
status: 400,
statusText: 'Bad Request',
data: 'No project ID received',
},
}
}

const urlEnd = typeof args === 'string' ? args : args.url
// 构造一个动态生成的 url 部分
const adjustedUrl = `project/${projectId}/${urlEnd}`
const adjustedArgs =
typeof args === 'string' ? adjustedUrl : { ...args, url: adjustedUrl }
// 提供修正后的 url 和其他参数给原始基础查询
return rawBaseQuery(adjustedArgs, api, extraOptions)
}

export const api = createApi({
baseQuery: dynamicBaseQuery,
endpoints: (builder) => ({
getPosts: builder.query<Post[], void>({
query: () => 'posts',
}),
}),
})

export const { useGetPostsQuery } = api

/*
当 Redux 状态中有一个 `projectId` 为 500 时使用 `useGetPostsQuery()`,
将会发送一个请求到 www.my-cool-site.com/project/500/posts
*/

示例 - transformResponse

解包深度嵌套的 GraphQL 数据

GraphQL 转换示例
// 文件: graphqlBaseQuery.ts noEmit
import { BaseQueryFn } from '@reduxjs/toolkit/query'
declare const graphqlBaseQuery: (args: { baseUrl: string }) => BaseQueryFn
declare const gql: (literals: TemplateStringsArray) => void
export { graphqlBaseQuery, gql }

// 文件: graphqlApi.ts
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,
}),
}),
})

使用 createEntityAdapter 标准化数据

在下面的示例中,transformResponsecreateEntityAdapter 一起使用,以在将数据存储在缓存中之前对其进行标准化。

对于如下的响应:

[
{ 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" },
}
}
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

示例 - queryFn

使用第三方 SDK

许多服务,如 Firebase 和 Supabase,提供了自己的 SDK 来进行请求。你可以在 queryFn 中使用这些 SDK 方法:

基础的第三方 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 方便地已经有了 `data` 和 `error` 字段
const { data, error } = await supabase.from('blogs').select()
if (error) {
return { error }
}
return { data }
},
}),
}),
})

你也可以尝试创建一个使用 SDK 的自定义基础查询,并定义将方法名或参数传入该基础查询的端点。

使用无操作的 queryFn

在某些情况下,你可能希望有一个 querymutation,其中发送请求或返回数据对于情况并不相关。这样的情况可能是为了利用 invalidatesTags 属性来强制重新获取已提供给缓存的特定 tags

另请参见 提供错误给缓存 以查看此类情况的更多细节和示例,以 '重新获取错误的查询'。

使用无操作的 queryFn
// 文件: types.ts noEmit
export interface Post {
id: number
name: string
}
export interface User {
id: number
name: string
}

// 文件: api.ts
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>({
// 这里的查询不相关,所以使用了一个返回 `null` 的 `queryFn`
queryFn: () => ({ data: null }),
// 这个 mutation 利用了标签失效行为来触发
// 任何提供 'Post' 或 'User' 标签的查询重新获取,如果查询
// 当前订阅了缓存的数据
invalidatesTags: ['Post', 'User'],
}),
}),
})

无初始请求的数据流

RTK Query 提供了一个端点发送初始数据请求的能力,然后用定期的流式更新进行进一步的缓存数据更新。然而,初始请求是可选的,你可能希望在没有任何初始请求被触发的情况下使用流式更新。

在下面的示例中,一个 queryFn 被用来用一个空数组填充缓存数据,没有发送初始请求。数组稍后通过 onCacheEntryAdded 端点选项使用流式更新进行填充,接收到数据时更新缓存数据。

无初始请求的数据流
// 文件: types.ts noEmit
export interface Message {
id: number
channel: 'general' | 'redux'
userName: string
text: string
}

// 文件: api.ts
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>({
// 这里的查询不相关,因为数据将通过流式更新提供。
// 使用返回空数组的 queryFn,内容将通过
// 下面的流式更新填充,当它们被接收时。
queryFn: () => ({ data: [] }),
async onCacheEntryAdded(arg, { updateCachedData, cacheEntryRemoved }) {
const ws = new WebSocket('ws://localhost:8080')
// 当从 websocket 接收到消息时,填充数组
ws.addEventListener('message', (event) => {
updateCachedData((draft) => {
draft.push(JSON.parse(event.data))
})
})
await cacheEntryRemoved
ws.close()
},
}),
}),
})

使用单个查询执行多个请求

在下面的示例中,编写了一个查询来获取随机用户的所有帖子。这是通过首先请求一个随机用户,然后获取该用户的所有帖子来完成的。使用 queryFn 允许将两个请求包含在一个查询中,避免在组件代码中链接该逻辑。

使用单个查询执行多个请求
// 文件: types.ts noEmit
export interface Post {}
export interface User {
id: number
}

// 文件: api.ts
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) {
// 获取一个随机用户
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 }
},
}),
}),
})