跳到主要内容

 

自定义查询

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

使用 baseQuery 自定义查询​

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

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

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

参见 baseQuery API Reference。

实现一个自定义的 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 函数参数​

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 函数只需要有最小的返回值才能有效;一个带有 data 或 error 属性的对象。由用户决定如何使用提供的参数,以及在函数内部如何处理请求。

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 函数本身(arg,api,extraOptions 和 baseQuery)。类似于 baseQuery,它预期返回一个具有 data 或 error 属性的对象,或者一个解析为返回此类对象的 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。

信息

一个钩子可能同时返回 data 和 error。默认情况下,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 标准化数据​

在下面的示例中,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" },
}
}
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​

在某些情况下,你可能希望有一个 query 或 mutation,其中发送请求或返回数据对于情况并不相关。这样的情况可能是为了利用 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 }
},
}),
}),
})