自定义查询
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 聊天 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
属性的对象,或者返回一个解析为返回此类对象的 promise。
基本 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 Unauthorized
错误时,发送额外的请求以尝试刷新授权令牌,并在重新授权后重新尝试初始查询。
- 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 Unauthorized
错误而失败时,多次调用 '/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 即可。
钩子可以同时返回 data
和 error
。默认情况下,RTK Query 会将最后一个“良好”结果保留在 data
中,直到它可以被更新或垃圾回收。
退出错误重试
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 或时间戳)的情况下很有用。
在这种情况下,返回值将如下所示
- 包含元信息的预期成功结果格式
return { data: YourData, meta: YourMeta }
- 包含元信息的预期错误结果格式
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 状态构建动态基本 URL
在某些情况下,您可能希望从 Redux 状态中的属性确定动态更改的基本 URL。baseQuery
可以访问 getState
方法,该方法在调用时提供当前存储状态。这可用于使用部分 URL 字符串和存储状态中的适当数据来构建所需的 URL。
- 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 的自定义基本查询,并定义将方法名称或参数传递到该基本查询的端点。
使用无操作 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://localhost: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://localhost: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 }
},
}),
}),
})