跳到主要内容

 

fetchBaseQuery

这是一个围绕 fetch 的非常小的包装器,旨在简化 HTTP 请求。它并不是 axiossuperagent 或任何其他更重量级库的全面替代品,但它将覆盖你的绝大多数 HTTP 请求需求。

fetchBaseQuery 是一个工厂函数,生成与 RTK Query 的 baseQuery 配置选项兼容的数据获取方法。它接受 fetch 的 RequestInit 接口的所有标准选项,以及 baseUrlprepareHeaders 函数、可选的 fetch 函数、paramsSerializer 函数和 timeout

基本使用

要使用它,当你创建 API 服务定义时导入它,调用 fetchBaseQuery(options),并将结果作为 createApi 中的 baseQuery 字段传递:

src/services/pokemon.ts
// 或者从 '@reduxjs/toolkit/query/react' 导入
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'

export const pokemonApi = createApi({
// 为下面的每个端点设置 baseUrl
baseQuery: fetchBaseQuery({ baseUrl: 'https://pokeapi.co/api/v2/' }),
endpoints: (builder) => ({
getPokemonByName: builder.query({
// 将发出像 https://pokeapi.co/api/v2/pokemon/bulbasaur 这样的请求
query: (name: string) => `pokemon/${name}`,
}),
updatePokemon: builder.mutation({
query: ({ name, patch }) => ({
url: `pokemon/${name}`,
// 当执行变异时,你通常使用 REST 端点的 PATCH/PUT/POST/DELETE 方法
method: 'PATCH',
// fetchBaseQuery 自动将 `content-type: application/json` 添加到
// Headers 并调用 `JSON.stringify(patch)`
body: patch,
}),
}),
}),
})

签名

fetchBaseQuery 签名
type FetchBaseQuery = (
args: FetchBaseQueryArgs,
) => (
args: string | FetchArgs,
api: BaseQueryApi,
extraOptions: ExtraOptions,
) => FetchBaseQueryResult

type FetchBaseQueryArgs = {
baseUrl?: string
prepareHeaders?: (
headers: Headers,
api: Pick<
BaseQueryApi,
'getState' | 'extra' | 'endpoint' | 'type' | 'forced'
>,
) => MaybePromise<Headers | void>
fetchFn?: (
input: RequestInfo,
init?: RequestInit | undefined,
) => Promise<Response>
paramsSerializer?: (params: Record<string, any>) => string
isJsonContentType?: (headers: Headers) => boolean
jsonContentType?: string
timeout?: number
} & RequestInit

type FetchBaseQueryResult = Promise<
| {
data: any
error?: undefined
meta?: { request: Request; response: Response }
}
| {
error: {
status: number
data: any
}
data?: undefined
meta?: { request: Request; response: Response }
}
>

参数

baseUrl

(必需)

通常是像 https://api.your-really-great-app.com/v1/ 这样的字符串。如果你不提供 baseUrl,它将默认为从发出请求的地方开始的相对路径。你最有可能 总是 指定这个

prepareHeaders

(可选)

允许你在每个请求上注入头。你可以在端点级别指定头,但你通常会想在这里设置常见的头,如 authorization。作为一种便利机制,第二个参数允许你使用 getState 来访问你的 redux 存储,以便你在那里存储你需要的信息,如 auth token。此外,它提供了对 extraendpointtypeforced 的访问,以解锁更精细的条件行为。

你可以直接修改 headers 参数,返回它是可选的。

prepareHeaders 签名
type prepareHeaders = (
headers: Headers,
api: {
getState: () => unknown
extra: unknown
endpoint: string
type: 'query' | 'mutation'
forced: boolean | undefined
},
) => Headers | void

paramsSerializer

(可选)

一个可以用来对传入 params 的数据应用自定义转换的函数。如果你不提供这个,params 将直接给到 new URLSearchParams()。在一些 API 集成中,你可能需要利用这个来使用像 query-string 库这样的东西来支持不同的数组类型。

fetchFn

(可选)

一个覆盖窗口默认的 fetch 函数。在 SSR 环境中可能很有用,你可能需要利用 isomorphic-fetchcross-fetch

timeout

(可选)

一个以毫秒为单位的数字,表示请求在超时前可以花费的最大时间。

isJsonContentType

(可选)

一个接收 Headers 对象并确定 FetchArgs 参数的 body 字段应通过 JSON.stringify() 进行字符串化的回调。

默认实现检查 content-type 头,并将匹配像 "application/json""application/vnd.api+json" 这样的值。

jsonContentType

(可选)

当自动为具有可json化主体的请求设置 content-type 头部,且没有明确的 content-type 头部时使用。默认为 "application/json"

常见使用模式

在请求上设置默认头部

prepareHeaders 的最常见用例是为你的 API 请求自动包含 authorization 头部。

// 文件: store.ts noEmit
export type RootState = { auth: { token: string } }

// 文件: baseQuery.ts
import { fetchBaseQuery } from '@reduxjs/toolkit/query'
import type { RootState } from './store'

const baseQuery = fetchBaseQuery({
baseUrl: '/',
prepareHeaders: (headers, { getState }) => {
const token = (getState() as RootState).auth.token

// 如果我们在状态中设置了一个令牌,我们假设我们应该传递它。
if (token) {
headers.set('authorization', `Bearer ${token}`)
}

return headers
},
})

单个查询选项

你可以在每个请求的基础上定义更多的行为。query 字段可以返回一个包含 RequestInit 接口可用的默认 fetch 选项的对象,以及以下额外选项:

端点请求选项
interface FetchArgs extends RequestInit {
url: string
params?: Record<string, any>
body?: any
responseHandler?:
| 'json'
| 'text'
| `content-type`
| ((response: Response) => Promise<any>)
validateStatus?: (response: Response, body: any) => boolean
timeout?: number
}

const defaultValidateStatus = (response: Response) =>
response.status >= 200 && response.status <= 299

设置主体

默认情况下,fetchBaseQuery 假设你做的每个请求都将是 json,所以在这些情况下,你只需要设置 url 并在适当的时候传递一个 body 对象。对于其他实现,你可以手动设置 Headers 来指定内容类型。

json

 // 省略
endpoints: (builder) => ({
updateUser: builder.query({
query: (user: Record<string, string>) => ({
url: `users`,
method: 'PUT',
body: user // 主体自动转换为带有正确头部的json
}),
}),

text

 // 省略
endpoints: (builder) => ({
updateUser: builder.query({
query: (user: Record<string, string>) => ({
url: `users`,
method: 'PUT',
headers: {
'content-type': 'text/plain',
},
body: user
}),
}),

设置查询字符串

fetchBaseQuery 提供了一个简单的机制,通过将对象传递给 new URLSearchParms()object 转换为序列化的查询字符串。如果这不符合你的需求,你有两个选择:

  1. paramsSerializer 选项传递给 fetchBaseQuery 以应用自定义转换
  2. 构建你自己的查询字符串并在 url 中设置它
 // 省略
endpoints: (builder) => ({
updateUser: builder.query({
query: (user: Record<string, string>) => ({
url: `users`,
// 假设没有指定 `paramsSerializer`,用户对象会自动转换
// 并生成一个像 /api/users?first_name=test&last_name=example 的url
params: user
}),
}),

解析响应

默认情况下,fetchBaseQuery 假设你得到的每个 Response 都将被解析为 json。如果你不希望这样,你可以通过指定一个替代的响应处理器,如 text,或者完全控制并使用一个接受原始 Response 对象的自定义函数来自定义行为 - 允许你使用任何 Response 方法

responseHandler 字段可以是:

type ResponseHandler =
| 'content-type'
| 'json'
| 'text'
| ((response: Response) => Promise<any>)

"json""text" 值指示 fetchBaseQuery 对应的获取响应方法来读取主体。content-type 将检查头部字段以首先确定这是否看起来像 JSON,然后使用其中的两种方法之一。回调允许你自己处理主体。

将响应解析为文本
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'

export const customApi = createApi({
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => ({
url: `users`,
// 这与传递 'text' 相同
responseHandler: (response) => response.text(),
}),
}),
}),
})
关于返回未定义主体的响应的注意事项

如果你向一个只返回 200 和未定义主体的 API 发送 json 请求,fetchBaseQuery 将把它作为 undefined 传递,并且不会尝试将其解析为 json。这在一些 API 中很常见,特别是在 delete 请求中。

处理非标准响应状态码

默认情况下,fetchBaseQueryreject 任何没有 2xx 状态码的 Response 并将其设置为 error。这与你最有可能经历过的 axios 和其他流行库的行为相同。如果你正在处理一个非标准的 API,你可以使用 validateStatus 选项来自定义这种行为。

使用自定义 validateStatus
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'

export const customApi = createApi({
// 为下面的每个端点设置 baseUrl
baseQuery: fetchBaseQuery({ baseUrl: '/api/' }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => ({
url: `users`,
// 示例:我们有一个后端 API 总是返回 200,
// 但在出现错误时设置了一个 `isError` 属性。
validateStatus: (response, result) =>
response.status === 200 && !result.isError,
}),
}),
}),
})

为请求添加自定义超时

默认情况下,fetchBaseQuery 没有设置默认超时值,这意味着你的请求将保持挂起状态,直到你的 api 解决请求或达到浏览器的默认超时(通常为5分钟)。大多数时候,这不是你想要的。当使用 fetchBaseQuery 时,你可以在 baseQuery 或单个端点上设置 timeout。当指定两个选项时,端点值将优先。

设置超时值
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query'

export const api = createApi({
// 设置默认超时为10秒
baseQuery: fetchBaseQuery({ baseUrl: '/api/', timeout: 10000 }),
endpoints: (builder) => ({
getUsers: builder.query({
query: () => ({
url: `users`,
// 示例:我们知道用户端点是 _非常快_ 的,因为它总是被缓存。
// 我们可以假设如果超过 > 1000ms,那么肯定出了问题,我们应该中止请求。
timeout: 1000,
}),
}),
}),
})