createApi
createApi
是 RTK Query 功能的核心。它允许你定义一组"端点(endpoints)",用于描述如何从后端 API 和其他异步数据源获取数据,包括如何获取和转换数据的配置。它生成一个 "API slice" 结构,其中包含 Redux 逻辑(以及可选的 React hooks),为你封装了数据获取和缓存过程。
通常,你应该为应用程序需要通信的每个基础 URL 只创建一个 API slice。例如,如果你的网站从 /api/posts
和 /api/users
获取数据,你应该创建一个以 /api/
为基础 URL 的单一 API slice,并为 posts
和 users
分别定义端点。这样可以通过在端点之间定义标签关系来有效利用自动重新获取功能。
这是因为:
- 自动标签失效只在单个 API slice 内工作。如果有多个 API slice,自动失效功能将无法在它们之间工作。
- 每次调用
createApi
都会生成自己的中间件,并且添加到 store 的每个中间件都会对每个分发的 action 进行检查。这会产生累积的性能开销。因此,如果调用了 10 次createApi
并向 store 添加了 10 个独立的 API 中间件,性能会明显变慢。
出于可维护性考虑,你可能希望将端点定义分散到多个文件中,同时仍然保持一个包含所有这些端点的单一 API slice。参见代码拆分了解如何使用 injectEndpoints
属性将其他文件中的 API 端点注入到单个 API slice 定义中。
// 文件: src/services/types.ts noEmit
export type Pokemon = {}
// 文件: src/services/pokemon.ts
// 需要使用 React 特定的入口点以允许生成 React hooks
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import type { Pokemon } from './types'
// 使用基础 URL 和预期的端点定义一个服务
export const pokemonApi = createApi({
reducerPath: 'pokemonApi',
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (build) => ({
getPokemonByName: build.query<Pokemon, string>({
query: (name) => `pokemon/${name}`,
}),
}),
})
// 导出 hooks 以在函数组件中使用,
// 这些 hooks 是基于定义的端点自动生成的
export const { useGetPokemonByNameQuery } = pokemonApi
createApi
参数
createApi
接受一个包含以下选项的配置对象参数:
baseQuery(args: InternalQueryArgs, api: BaseQueryApi, extraOptions?: DefinitionExtraOptions): any;
endpoints(build: EndpointBuilder<InternalQueryArgs, TagTypes>): Definitions;
extractRehydrationInfo?: (
action: UnknownAction,
{
reducerPath,
}: {
reducerPath: ReducerPath
}
) =>
| undefined
| CombinedState<Definitions, TagTypes, ReducerPath>
tagTypes?: readonly TagTypes[];
reducerPath?: ReducerPath;
serializeQueryArgs?: SerializeQueryArgs<InternalQueryArgs>;
keepUnusedDataFor?: number; // 值以秒为单位
refetchOnMountOrArgChange?: boolean | number; // 值以秒为单位
refetchOnFocus?: boolean;
refetchOnReconnect?: boolean;
baseQuery
如果没有指定 queryFn
选项,每个端点使用的基础查询。RTK Query 导出了一个名为 fetchBaseQuery 的实用程序,作为 fetch
的轻量级包装,用于常见的用例。如果 fetchBaseQuery
无法满足你的需求,请参阅 自定义查询。
baseQuery 函数参数
args
- 给定端点的query
函数的返回值api
-BaseQueryApi
对象包含:signal
- 一个AbortSignal
对象,可用于中止 DOM 请求和/或读取请求是否已中止。abort
-signal
附带的abort()
方法。dispatch
- 对应 Redux store 的store.dispatch
方法getState
- 一个可以调用以访问当前 store 状态的函数extra
- 作为 thunk.extraArgument 提供给 configureStore getDefaultMiddleware 选项。endpoint
- 端点的名称。type
- 请求类型(query
或mutation
)。forced
- 指示查询是否已被强制。queryCacheKey
- 计算出的查询缓存键。
extraOptions
- 为给定端点提供的可选extraOptions
属性的值
baseQuery 函数签名
export type BaseQueryFn<
Args = any,
Result = unknown,
Error = unknown,
DefinitionExtraOptions = {},
Meta = {},
> = (
args: Args,
api: BaseQueryApi,
extraOptions: DefinitionExtraOptions,
) => MaybePromise<QueryReturnValue<Result, Error, Meta>>
export interface BaseQueryApi {
signal: AbortSignal
abort: (reason?: string) => void
dispatch: ThunkDispatch<any, any, any>
getState: () => unknown
extra: unknown
endpoint: string
type: 'query' | 'mutation'
forced?: boolean
}
export type QueryReturnValue<T = unknown, E = unknown, M = unknown> =
| {
error: E
data?: undefined
meta?: M
}
| {
error?: undefined
data: T
meta?: M
}
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
// ...端点
}),
})
endpoints
端点只是你想对服务器执行的一组操作。你使用构建器语法将它们定义为一个对象。有两种基本的端点类型:query
和 mutation
。
参见端点定义参数了解各个属性的详细信息。
查询端点定义
查询端点(使用 build.query()
定义)用于缓存从服务器获取的数据。
你必须指定一个 query
字段(将使用 API 的 baseQuery
进行请求),或一个带有你自己异步逻辑的 queryFn
函数。所有其他字段都是可选的。
export type QueryDefinition<
QueryArg,
BaseQuery extends BaseQueryFn,
TagTypes extends string,
ResultType,
ReducerPath extends string = string,
> = {
query(arg: QueryArg): BaseQueryArg<BaseQuery>
/* `query` 或 `queryFn` 可以存在,但不能同时存在 */
queryFn(
arg: QueryArg,
api: BaseQueryApi,
extraOptions: BaseQueryExtraOptions<BaseQuery>,
baseQuery: (arg: Parameters<BaseQuery>[0]) => ReturnType<BaseQuery>,
): MaybePromise<QueryReturnValue<ResultType, BaseQueryError<BaseQuery>>>
/* transformResponse 仅在 `query` 中可用,不适用于 `queryFn` */
transformResponse?(
baseQueryReturnValue: BaseQueryResult<BaseQuery>,
meta: BaseQueryMeta<BaseQuery>,
arg: QueryArg,
): ResultType | Promise<ResultType>
/* transformErrorResponse 仅在 `query` 中可用,不适用于 `queryFn` */
transformErrorResponse?(
baseQueryReturnValue: BaseQueryError<BaseQuery>,
meta: BaseQueryMeta<BaseQuery>,
arg: QueryArg,
): unknown
extraOptions?: BaseQueryExtraOptions<BaseQuery>
providesTags?: ResultDescription<
TagTypes,
ResultType,
QueryArg,
BaseQueryError<BaseQuery>
>
keepUnusedDataFor?: number
onQueryStarted?(
arg: QueryArg,
{
dispatch,
getState,
extra,
requestId,
queryFulfilled,
getCacheEntry,
updateCachedData, // 仅适用于查询端点
}: QueryLifecycleApi,
): Promise<void>
onCacheEntryAdded?(
arg: QueryArg,
{
dispatch,
getState,
extra,
requestId,
cacheEntryRemoved,
cacheDataLoaded,
getCacheEntry,
updateCachedData, // 仅适用于查询端点
}: QueryCacheLifecycleApi,
): Promise<void>
}
无限查询端点定义
无限查询端点(使用 build.infiniteQuery()
定义)用于缓存从服务器获取的多页数据集。它们具有与标准查询端点相同的回调和选项,但还需要一个额外的 infiniteQueryOptions
字段来指定如何计算每页的唯一参数。
对于无限查询端点,查询参数用于缓存键,而页面参数用于获取特定页面。例如,Pokemon API 端点可能有一个字符串查询参数 "fire"
,但使用页码作为参数来确定要从结果中获取的页面。query
和 queryFn
方法将接收一个组合的 {queryArg, pageParam}
对象作为参数,而不是单独的 queryArg
。
export type PageParamFunction<DataType, PageParam> = (
firstPage: DataType,
allPages: Array<DataType>,
firstPageParam: PageParam,
allPageParams: Array<PageParam>,
) => PageParam | undefined | null
type InfiniteQueryCombinedArg<QueryArg, PageParam> = {
queryArg: QueryArg
pageParam: PageParam
}
export type InfiniteQueryDefinition<
QueryArg,
PageParam,
BaseQuery extends BaseQueryFn,
TagTypes extends string,
ResultType,
ReducerPath extends string = string,
> =
// 无限查询具有与查询端点相同的选项,
// 但存储 `{pages, pageParams}` 结构,并接收一个对象
// 其中包含 `{queryArg, pageParam}` 作为 `query` 和 `queryFn` 的参数。
QueryDefinition<
InfiniteQueryCombinedArg<QueryArg, PageParam>,
BaseQuery,
TagTypes,
InfiniteData<ResultType>
> & {
/**
* 配置无限查询行为的必需选项。
* `initialPageParam` 和 `getNextPageParam` 是必需的,
* 以确保无限查询能够正确获取下一页数据。
* 使用端点时可以指定 `initialPageparam`,
* 以覆盖默认值。
*/
infiniteQueryOptions: {
/**
* 用于首次页面获取的初始页面参数。
*/
initialPageParam: PageParam
/**
* 此函数是自动获取无限查询下一页游标所必需的。
* 结果还将用于确定 `hasNextPage` 的值。
*/
getNextPageParam: PageParamFunction<DataType, PageParam>
/**
* 此函数可以设置为自动获取无限查询的上一页游标。
* 结果还将用于确定 `hasPreviousPage` 的值。
*/
getPreviousPageParam?: PageParamFunction<DataType, PageParam>
/**
* 如果指定,则一次仅保留这么多页在缓存中。
* 如果获取了额外的页面,较旧的页面将在其他方向上
* 从缓存中删除。
*/
maxPages?: number
}
}
Mutation 端点定义
Mutation 端点(使用 build.mutation()
定义)用于向服务器发送更新,并强制使查询端点失效和重新获取。
与查询一样,你必须指定 query
选项或 queryFn
异步方法。
export type MutationDefinition<
QueryArg,
BaseQuery extends BaseQueryFn,
TagTypes extends string,
ResultType,
ReducerPath extends string = string,
Context = Record<string, any>,
> = {
query(arg: QueryArg): BaseQueryArg<BaseQuery>
/* `query` 或 `queryFn` 可以存在,但不能同时存在 */
queryFn(
arg: QueryArg,
api: BaseQueryApi,
extraOptions: BaseQueryExtraOptions<BaseQuery>,
baseQuery: (arg: Parameters<BaseQuery>[0]) => ReturnType<BaseQuery>,
): MaybePromise<QueryReturnValue<ResultType, BaseQueryError<BaseQuery>>>
/* transformResponse 仅在 `query` 中可用,不适用于 `queryFn` */
transformResponse?(
baseQueryReturnValue: BaseQueryResult<BaseQuery>,
meta: BaseQueryMeta<BaseQuery>,
arg: QueryArg,
): ResultType | Promise<ResultType>
/* transformErrorResponse 仅在 `query` 中可用,不适用于 `queryFn` */
transformErrorResponse?(
baseQueryReturnValue: BaseQueryError<BaseQuery>,
meta: BaseQueryMeta<BaseQuery>,
arg: QueryArg,
): unknown
extraOptions?: BaseQueryExtraOptions<BaseQuery>
invalidatesTags?: ResultDescription<TagTypes, ResultType, QueryArg>
onQueryStarted?(
arg: QueryArg,
{
dispatch,
getState,
extra,
requestId,
queryFulfilled,
getCacheEntry,
}: MutationLifecycleApi,
): Promise<void>
onCacheEntryAdded?(
arg: QueryArg,
{
dispatch,
getState,
extra,
requestId,
cacheEntryRemoved,
cacheDataLoaded,
getCacheEntry,
}: MutationCacheLifecycleApi,
): Promise<void>
}
端点如何使用
定义一个像 getPosts
这样的键时,重要的是要知道这个名称将从 api
中导出,并且可以在 api.endpoints.getPosts.useQuery()
, api.endpoints.getPosts.initiate()
和 api.endpoints.getPosts.select()
下引用。同样的事情适用于 mutation
,但它们引用 useMutation
而不是 useQuery
。
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
interface Post {
id: number
name: string
}
type PostsResponse = Post[]
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Posts'],
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => 'posts',
providesTags: (result) =>
result ? result.map(({ id }) => ({ type: 'Posts', id })) : [],
}),
addPost: build.mutation<Post, Partial<Post>>({
query: (body) => ({
url: `posts`,
method: 'POST',
body,
}),
invalidatesTags: ['Posts'],
}),
}),
})
// 自动生成的 hooks
export const { useGetPostsQuery, useAddPostMutation } = api
// 可能的导出
export const { endpoints, reducerPath, reducer, middleware } = api
// reducerPath, reducer, middleware 仅用于 store 配置
// 端点将具有:
// endpoints.getPosts.initiate(), endpoints.getPosts.select(), endpoints.getPosts.useQuery()
// endpoints.addPost.initiate(), endpoints.addPost.select(), endpoints.addPost.useMutation()
// 参见 `createApi` 概述了解 _所有导出_
extractRehydrationInfo
传递每个调度动作的函数。如果这返回了除 undefined
以外的东西,
那么返回值将用于重新填充已完成和错误的查询。
// 代码块元数据 标题="next-redux-wrapper 重新填充示例"
import type { Action, PayloadAction } from '@reduxjs/toolkit'
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
import { HYDRATE } from 'next-redux-wrapper'
type RootState = any; // 通常从状态推断
function isHydrateAction(action: Action): action is PayloadAction<RootState> {
return action.type === HYDRATE
}
export const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
extractRehydrationInfo(action, { reducerPath }): any {
if (isHydrateAction(action)) {
return action.payload[reducerPath]
}
},
endpoints: (build) => ({
// 省略
}),
})
tagTypes
字符串标签类型名称的数组。指定标签类型是可选的,但你应该定义它们,以便它们可以用于缓存和失效。定义标签类型时,你可以在配置 endpoints 时使用 providesTags
提供 它们,并使用 invalidatesTags
使其失效。
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Post', 'User'],
endpoints: (build) => ({
// ...endpoints
}),
})
reducerPath
reducerPath
是你的服务在存储中挂载的 唯一 键。如果你在应用程序中多次调用 createApi
,每次都需要提供一个唯一的值。默认为 'api'
。
// 代码块元数据 标题="apis.js"
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query';
const apiOne = createApi({
reducerPath: 'apiOne',
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (builder) => ({
// ...endpoints
}),
});
const apiTwo = createApi({
reducerPath: 'apiTwo',
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (builder) => ({
// ...endpoints
}),
});
serializeQueryArgs
如果你需要出于任何原因更改缓存键的创建,可以接受一个自定义函数。
默认情况下,此函数将获取查询参数,在适用的情况下对对象键进行排序,将结果字符串化,并将其与端点名称连接起来。这会根据参数+端点名称的组合(忽略对象键顺序)创建一个缓存键,这样调用任何给定端点时使用相同的参数将产生相同的缓存键。
invalidationBehavior
默认为 'immediately'
。此设置允许你控制在突变后何时使标签无效。
'immediately'
:突变完成后,查询立即失效,即使它们正在运行。 如果查询提供了在运行时失效的标签,它将不会被重新获取。'delayed'
:只有在所有查询和突变都解决后,才会发生失效。 这确保了查询总是正确地失效,并自动地“批处 理”并发突变的失效。 注意,如果你不断地运行一些查询(或突变),这可能会无限期地延迟标签失效。
keepUnusedDataFor
默认为 60
(此值以秒为单位)。这是 RTK Query 在最后一个组件取消订阅 之后 保留你的数据缓存的时间。例如,如果你查询一个端点,然后卸载组件,然后在给定的时间框架内挂载另一个发出相同请求的组件,最近的值将从缓存中提供。
// 代码块元数据 标题="keepUnusedDataFor 示例"
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
interface Post {
id: number
name: string
}
type PostsResponse = Post[]
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => 'posts',
keepUnusedDataFor: 5
})
})
})
refetchOnMountOrArgChange
默认为 false
。此设置允许你控制如果已经有缓存结果可用,RTK Query 是否只提供缓存结果,或者如果设置为 true
或者自上次成功查询结果以来已经过去了足够的时间,它应该 refetch
。
false
- 不会导致执行查询,除非 它还不存在。true
- 当添加一个新的查询订阅者时,总是会重新获取。行为与调用refetch
回调或在动作创建器中传递forceRefetch: true
相同。number
- 值以秒为单位。如果提供了一个数字并且缓存中存在一个现有的查询,它将比较当前时间与最后一次满足的时间戳,并且只有在过去了足够的时间后才会重新获取。
如果你在 skip: true
旁边指定了此选项,这个选项在 skip
为 false 之前不会被评估。
你 可以在 createApi
中全局设置此选项,但也可以覆盖默认值,通过传递 refetchOnMountOrArgChange
给每个单独的 hook 调用或类似地通过传递 forceRefetch: true
在分发 initiate
action 时进行更细粒度的控制。
refetchOnFocus
默认为 false
。此设置允许你控制当应用程序窗口重新获得焦点后,RTK Query 是否会尝试重新获取所有订阅的查询。
如果你在 skip: true
旁边指定了此选项,这个选项在 skip
为 false 之前不会被评估。
注意:需要调用 setupListeners
。
你可以在 createApi
中全局设置此选项,但也可以覆盖默认值,通过传递 refetchOnFocus
给每个单独的 hook 调用或在分发 initiate
action 时进行更细粒度的控制。
如果在手动分发查询时指定 track: false
,RTK Query 将无法自动重新获取。
refetchOnReconnect
默认为 false
。此设置允许你控制当重新获得网络连接后,RTK Query 是否会尝试重新获取所有订阅的查询。
如果你在 skip: true
旁边指定了此选项,这个选项在 skip
为 false 之前不会被评估。
注意:需要调用 setupListeners
。
你可以在 createApi
中全局设置此选项,但也可以覆盖默认值,通过传递 refetchOnReconnect
给每个单独的 hook 调用或在分发 initiate
action 时进行更细粒 度的控制。
如果在手动分发查询时指定 track: false
,RTK Query 将无法自动重新获取。
端点定义参数
query
(如果未提供 queryFn
,则必需)
export type query = <QueryArg>(
arg: QueryArg,
) => string | Record<string, unknown>
// 使用 `fetchBaseQuery`
export type query = <QueryArg>(arg: QueryArg) => string | FetchArgs
query
可以是一个返回 string
或传递给你的 baseQuery
的 object
的函数。如果你使用 fetchBaseQuery,这可以返回一个 string
或 FetchArgs
属性的 object
。如果你使用自定义的 baseQuery
,你可以根据自己的喜好定制这个行为。
// 代码块元数据 标题="query 示例"
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
interface Post {
id: number
name: string
}
type PostsResponse = Post[]
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
tagTypes: ['Post'],
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => 'posts',
}),
addPost: build.mutation<Post, Partial<Post>>({
query: (body) => ({
url: `posts`,
method: 'POST',
body,
}),
invalidatesTags: [{ type: 'Post', id: 'LIST' }],
}),
})
})
queryFn
(如果未提供 query
,则必需)
可以用于替代 query
作为一个内联函数,该函数完全绕过了端点的 baseQuery
。
与 baseQuery
具有相同的参数,以及提供的 baseQuery
函数本身。预期返回一个包含 data
或 error
属性的对象,或一个解析为返回此类对象的 promise。
另请参见使用 queryFn 自定义查询。
queryFn(
arg: QueryArg,
api: BaseQueryApi,
extraOptions: BaseQueryExtraOptions<BaseQuery>,
baseQuery: (arg: Parameters<BaseQuery>[0]) => ReturnType<BaseQuery>
): MaybePromise<
| {
error: BaseQueryError<BaseQuery>
data?: undefined
}
| {
error?: undefined
data: ResultType
}
>
export interface BaseQueryApi {
signal: AbortSignal
dispatch: ThunkDispatch<any, any, any>
getState: () => unknown
}
queryFn
函数参数
args
- 调用查询时提供的参数api
-BaseQueryApi
对象,包含signal
,dispatch
和getState
属性signal
- 一个AbortSignal
对象,可用于中止 DOM 请求和/或读取请求是否已中止。dispatch
- 对应 Redux store 的store.dispatch
方法getState
- 一个可以调用以访问当前 store 状态的函数
extraOptions
- 为端点提供的可选extraOptions
属性的值baseQuery
- 提供给 api 本身的baseQuery
函数
// 代码块元数据 标题="基础 queryFn 示例"
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
interface Post {
id: number
name: string
}
type PostsResponse = Post[]
const api = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/' }),
endpoints: (build) => ({
getPosts: build.query<PostsResponse, void>({
query: () => 'posts',
}),
flipCoin: build.query<'heads' | 'tails', void>({
queryFn(arg, queryApi, extraOptions, baseQuery) {
const randomVal = Math.random()
if (randomVal < 0.45) {
return { data: 'heads' }
}
if (randomVal < 0.9) {
return { data: 'tails' }
}
return { error: { status: 500, statusText: 'Internal Server Error', data: "Coin landed on it's edge!" } }
}
})
})
})
infiniteQueryOptions
(仅适用于 infiniteQuery
端点)
infiniteQueryOptions
字段包括:
initialPageParam
: 用于首次请求的默认页面参数值,如果在使用站点未指定此值getNextPageParam
: 你必须提供的回调,以计算下一个页面参数,给定现有的缓存页面和页面参数getPreviousPageParam
: 用于计算上一页参数的可选回调,如果你尝试向后获取。maxPages
: 一次在缓存条目中保留的已获取页面的可选限制
transformResponse
(可选,不适用于 queryFn
)
用于操作查询或突变返回的数据的函数。
在某些情况下, 你可能希望在将查询返回的数据放入缓存之前对其进行操作。在这种情况下,你可以利用 transformResponse
。
另请参见使用 transformResponse
自定义查询响应
transformResponse: (response, meta, arg) =>
response.some.deeply.nested.collection
transformErrorResponse
(可选,不适用于 queryFn
)
用于操作失败的查询或突变返回的数据的函数。
在某些情况下,你可能希望在将查询返回的错误放入缓存之前对其进行操作。在这种情况下,你可以利用 transformErrorResponse
。
另请参见使用 transformErrorResponse
自定义查询响应
transformErrorResponse: (response, meta, arg) =>
response.data.some.deeply.nested.errorObject