fetchBaseQuery
这是一个围绕 fetch
的非常小的包装器,旨在简化 HTTP 请求。它并不是 axios
、superagent
或任何其他更重量级库的全面替代品,但它将覆盖你的绝大多数 HTTP 请求需求。
fetchBaseQuery
是一个工厂函数,生成与 RTK Query 的 baseQuery
配置选项兼容的数据获取方法。它接受 fetch 的 RequestInit
接口的所有标准选项,以及 baseUrl
、prepareHeaders
函数、可选的 fetch
函数、paramsSerializer
函数和 timeout
。
基本使用
要使用它,当你创建 API 服务定义时导入它,调用 fetchBaseQuery(options)
,并将结果作为 createApi
中的 baseQuery
字段传递:
// 或者从 '@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,
}),
}),
}),
})
签名
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。此外,它提供了对 extra
、endpoint
、type
和 forced
的访问,以解锁更精细的条件行为。
你可以直接修改 headers
参数,返回它是可选的。
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-fetch
或 cross-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
转换为序列化的查询字符串。如果这不符合你的需求,你有两个选择:
- 将
paramsSerializer
选项传递给fetchBaseQuery
以应用自定义转换 - 构建你自己的查询字符串并在
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
请求中。
处理非标准响应状态码
默认情况下,fetchBaseQuery
将 reject
任何没有 2xx
状态码的 Response
并将其设置为 error
。这与你最有可能经历过的 axios
和其他流行库的行为相同。如果你正在处理一个非标准的 API,你可以使用 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,
}),
}),
}),
})