UmiJS Max 内置 request 插件详解:基于 axios 的统一请求、错误处理与拦截器方案
【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi
本文基于 UmiJS 仓库中@umijs/max的 Request 插件官方文档(docs/docs/docs/max/request.en-US.md)撰写,系统讲解其构建时配置(dataField)、运行时配置(errorConfig、requestInterceptors、responseInterceptors)、request/useRequestAPI、请求取消机制,以及从 umi@3 的 umi-request 迁移到 axios 方案的全部要点;并结合仓库源码 packages/plugins/src/request.ts 还原每个配置项的底层实现,帮助你在实际项目中快速落地一套统一、可定制的网络请求方案。
方案概览:axios + ahooks useRequest
@umijs/max内置了 request 插件,提供基于 axios 的统一网络请求与错误处理方案,同时内置了 ahooks 的useRequest用于在组件内消费数据:
import { request, useRequest } from 'umi'; request; useRequest;从源码结构看,该插件在 packages/plugins/src/request.ts 中以key: 'request'注册,属于"按配置启用"(EnableBy.config)的插件:当项目在构建配置中声明了request字段,或在src/app.ts中导出了运行时request配置时,框架会自动生成临时的request.ts、types.d.ts等运行时文件(见该文件api.onGenerateFiles段落),从而把 axios 实例与 ahooks 的useRequest封装成一套统一 API。
仓库内可直接参考的示例包括:
- 基础用法示例:examples/with-request/app.ts、examples/with-request/pages/index.tsx;
- 完整
@umijs/max运行时配置示例:examples/max/app.ts; - Ant Design Pro 模板中的错误处理配置:examples/antd-pro-create/src/requestErrorConfig.ts。
配置
构建时配置:dataField
在构建配置中可以为useRequest配置dataField,默认值为data:
export default { request: { dataField: 'data' }, };该配置的主要目的是方便useRequest直接消费数据。如果你希望在消费数据时拿到后端的原始数据,需要把dataField配置为''(空字符串)。
例如,如果后端返回的数据结构如下:
{ success: true, data: 123, code: 1, }配置默认值时,useRequest拿到的data就是内层的123,而不是{ success, data, code }。
源码层面可以印证这一机制:插件在生成临时文件时读取api.config.request?.dataField(未配置时回退为'data'),并据此生成useRequest的formatResult逻辑——dataField为空时生成result => result(原样透传),否则生成result => result?.${dataField}(自动解包对应字段)。相关实现见 packages/plugins/src/request.ts。此外,dataField还会影响生成的ResultWithData类型与泛型,使useRequest返回值的data类型与配置保持一致。
运行时配置
在src/app.ts中,通过导出request配置项即可定制项目的请求行为:
import type { RequestConfig } from 'umi'; export const request: RequestConfig = { timeout: 1000, // other axios options you want errorConfig: { errorHandler(){ }, errorThrower(){ } }, requestInterceptors: [], responseInterceptors: [] };除errorConfig、requestInterceptors、responseInterceptors三项外,其余配置会直接透传给 axios 的请求配置(如timeout、headers、paramsSerializer等)。这里配置的所有规则对全部request与useRequest调用生效——因为运行时配置最终被传入axios.create(config)创建的全局 axios 实例,见 packages/plugins/src/request.ts。
errorConfig:errorThrower 与 errorHandler
如果想为请求设置统一的错误处理方案,就配置errorConfig:
errorThrower:接收后端返回的数据,负责抛出一个你自定义的错误。你可以在这里按业务需要处理后端数据;errorHandler:request会捕获errorThrower抛出的错误并执行该回调。它接收两个参数,第一个是被捕获的错误,第二个是该次请求的opts。
两者需要配合使用。文档同时提示:如果你觉得这种错误处理方式过于复杂,也可以直接在拦截器中实现自己的错误处理。
触发时机:
errorThrower底层是通过响应拦截器实现的,当响应的data.success为false时被调用。
源码印证见 packages/plugins/src/request.ts:插件在创建 axios 实例后会额外注册一个响应拦截器,当data?.success === false且配置了errorConfig.errorThrower时,调用errorThrower(data)抛出错误;抛出的错误随后走 axios 的 reject 链路,在request方法的catch分支中被errorHandler捕获处理(见 packages/plugins/src/request.ts)。这一实现也解释了为什么文档强调"若使用了 errorHandler,单次请求注册的响应拦截器将不生效,因为 errorHandler 会抛出 error"。
requestInterceptors
为请求方法添加"请求阶段"拦截器,传入一个数组,每个元素是一个拦截器,按顺序注册到 axios 实例上。拦截器写法与 axios 请求拦截器一致:接收请求配置并返回它。
推荐使用RequestConfig类型,以规范化的方式编写拦截器:
const request: RequestConfig = { requestInterceptors: [ // 直接写一个函数作为拦截器 (url, options) => { // do something return { url, options } }, // 元组:第一个元素是请求拦截器,第二个是错误处理 [(url, options) => {return { url, options }}, (error) => {return Promise.reject(error)}], // 数组:省略错误处理 [(url, options) => {return { url, options }}] ] }值得注意的是,为了兼容 umi-request 的使用习惯,插件同时允许 umi-request 风格的拦截器语法(即(url, options) => ({ url, options })双参数形式),尽管它可能通不过 TypeScript 的类型检查。源码中的兼容处理见 packages/plugins/src/request.ts:类型上定义了IRequestInterceptorAxios(单参数 config)与IRequestInterceptorUmiRequest(url + options 双参数)两种形态;运行时通过检查拦截器函数的length是否为 2 来区分两种写法并分别调用,见 packages/plugins/src/request.ts。
仓库中的 examples/with-request/app.ts 展示了多个请求拦截器注册的实例(含异步拦截器示例),可用于验证执行顺序行为。
responseInterceptors
为请求方法添加"响应阶段"拦截器,同样是传入数组、按顺序注册到 axios 实例。写法与 axios 响应拦截器一致:接收 axios response 并返回它。
const request: RequestConfig = { responseInterceptors: [ // 直接写一个函数作为拦截器 (response) => { // 无需异步读取响应体,可以直接从 data 读取 const { data = {} as any, config } = response; // do something return response }, // 元组:第一个元素是拦截器,第二个是错误处理 [(response) => {return response}, (error) => {return Promise.reject(error)}], // 数组:省略错误处理 [(response) => {return response}] ] }注意:拦截器按你数组的顺序注册,但执行顺序遵循 axios 约定——后添加的请求拦截器先执行,后添加的响应拦截器后执行。这也是 examples/with-request/app.ts 中三个拦截器以3 → 2 → 1顺序打印日志的原因。
API
useRequest
插件内置了 ahooks 的useRequest,让你在组件内简单消费数据:
import { useRequest } from 'umi'; export default function Page() { const { data, error, loading } = useRequest(() => { return services.getUserList('/api/test'); }); if (loading) { return <div>loading...</div>; } if (error) { return <div>{error.message}</div>; } return <div>{data.name}</div>; };在上述代码中,data并不是后端返回的原始数据,而是其内层的data(因为构建时配置默认值为'data',formatResult会自动解包,见 packages/plugins/src/request.ts 中useRequest的formatResult注入逻辑)。
需要说明:ahooks 已更新到 3.0,但为降低umi@3项目升级的难度,插件继续基于 ahooks 2.0 的@ahooksjs/use-request实现。
request
request接受除透传 axios 全部配置之外,还额外提供了四个属性:skipErrorHandler、getResponse、requestInterceptors、responseInterceptors:
request('/api/user', { params: { name : 1 }, timeout: 2000, // other axios options skipErrorHandler: true, getResponse: false, requestInterceptors: [], responseInterceptors: [], }各属性行为:
skipErrorHandler: true:让该次请求跳过统一错误处理;getResponse:默认request返回后端数据(即res.data);传入{ getResponse: true }则拿到 axios 完整响应结构。源码中对应resolve(getResponse ? res : res.data),见 packages/plugins/src/request.ts;- 单次
requestInterceptors/responseInterceptors:写法与运行时配置相同,但这里注册的拦截器是"一次性"的——源码会在请求完成(无论成功或失败)后调用interceptors.request.eject/interceptors.response.eject将其移除,见 packages/plugins/src/request.ts。同时,单次注册的拦截器注册时机晚于运行时配置的拦截器; - 注意:当项目配置了
errorHandler时,此处注册的响应拦截器将不生效,因为errorHandler会抛出 error。
IRequest的类型签名(含getResponse: true/false的重载区分返回AxiosResponse还是T)见 packages/plugins/src/request.ts。
RequestConfig
RequestConfig是帮助你规范化编写运行时配置的类型接口,导入时注意加type:
import type { RequestConfig } from 'umi'; export const request:RequestConfig = {};从源码看,其定义为AxiosRequestConfig的扩展,额外增加了errorConfig(errorHandler+errorThrower)、requestInterceptors、responseInterceptors四个字段,见 packages/plugins/src/request.ts。
取消请求:AbortController
使用标准 fetch API 的AbortController机制取消请求:
import { request } from '@umijs/max'; import { Button } from 'antd'; const controller = new AbortController(); const HomePage: React.FC = () => { const fetchData = async () => { const res = await request('/api/getData', { method: 'GET', signal: controller.signal }) } const cancelData = () => { controller.abort(); } return ( <> <Button onClick={fetchData}>send request</Button> <Button onClick={cancelData}>cancel request</Button> </> ); }; export default HomePage;由于signal属于 axios 原生支持的请求配置项,而运行时配置中除插件专属字段外全部透传给 axios,因此该能力可直接使用,无需额外插件支持。
从 umi@3 迁移到 umi@4
在umi@3到umi@4的升级中,官方停用了 umi-request,选择 axios 作为默认请求方案,相应地发生了几项功能变化。
运行时配置变化
umi@4的运行时配置与umi@3相比有显著变化:
export const request: RequestConfig = { errorConfig: { ++ errorHandler: () => {}, ++ errorThrower: () => {} -- errorPage: '', -- adaptor: ()=>{}, }; -- middlewares: [], ++ requestInterceptors: [], ++ responseInterceptors: [], ... // umi-request 与 axios 之间的差异。 };要点归纳:
- umi-request 的配置项变为 axios 的配置项;
- 移除了
middlewares中间件,可以用 axios 的 interceptors 实现相同功能; errorConfig移除了原有全部设置,新增errorHandler与errorThrower作为统一错误处理设置。
中间件替换规则:umi@3中间件中next()之前的内容放入requestInterceptors,next()之后的内容放入responseInterceptors:
// Middleware(umi@3) async function middleware(ctx, next) { const { url, options } = req; if (url.indexOf('/api') !== 0) { ctx.req.url = `/api/v1/${url}`; } await next(); if (!ctx.res.success) { // do something } } // Interceptor(umi@4) { requestInterceptors:[ (config) => { if (config.url.indexOf('/api') !== 0) { config.url = `/api/v1/${url}`; } return config; } ], responseInterceptors: [ (response) => { if(!response.data.success){ // do something } } ] }请求方法参数变化
umi-request 与 axios 在配置项上存在差异,迁移时两者文档需对照参考;由于插件对 umi-request 风格做了兼容(如前文双参数拦截器写法),大部分场景可直接平移。
GET 请求参数序列化差异
umi@3 默认用同一个 Key 序列化数组,而 umi@4 基于 axios,默认以方括号[]形式序列化:
// Umi@3 import { useRequest } from 'umi'; // a: [1,2,3] => a=1&a=2&a=3 // Umi@4 import { useRequest } from '@umijs/max'; // a: [1,2,3] => a[]=1&a[]=2&a[]=3如果希望保持 umi@3 的序列化形式,可以借助paramsSerializer(该字段属于 axios 配置,可直接写在运行时配置中):
// src/app.[ts|tsx] import queryString from 'query-string'; export const request: RequestConfig = { paramsSerializer(params) { return queryString.stringify(params); }, ... }完整运行时配置示例
下面给出一个完整的运行时配置示例,帮助你在项目中定制请求方案。该示例的错误处理方案脱胎于umi@3的内置错误处理;umi@4中官方将其移除以给用户更多自定义空间,如果你仍想沿用,可直接将该配置粘贴到项目中。你也可以完全通过响应拦截器编写自己的错误处理,并不局限于 errorConfig。
import { RequestConfig } from './request'; // 错误处理方案:错误类型 enum ErrorShowType { SILENT = 0, WARN_MESSAGE = 1, ERROR_MESSAGE = 2, NOTIFICATION = 3, REDIRECT = 9, } // 与后端约定的响应数据结构 interface ResponseStructure { success: boolean; data: any; errorCode?: number; errorMessage?: string; showType?: ErrorShowType; } // 运行时配置 export const request: RequestConfig = { // 统一请求设置 timeout: 1000, headers: {'X-Requested-With': 'XMLHttpRequest'}, // 错误处理:umi@3 的错误处理方案。 errorConfig: { // 抛错 errorThrower: (res: ResponseStructure) => { const { success, data, errorCode, errorMessage, showType } = res; if (!success) { const error: any = new Error(errorMessage); error.name = 'BizError'; error.info = { errorCode, errorMessage, showType, data }; throw error; // 抛出自定义错误 } }, // 捕获错误并处理 errorHandler: (error: any, opts: any) => { if (opts?.skipErrorHandler) throw error; // 由我们的 errorThrower 抛出的错误。 if (error.name === 'BizError') { const errorInfo: ResponseStructure | undefined = error.info; if (errorInfo) { const { errorMessage, errorCode } = errorInfo; switch (errorInfo.showType) { case ErrorShowType.SILENT: // 什么都不做 break; case ErrorShowType.WARN_MESSAGE: message.warn(errorMessage); break; case ErrorShowType.ERROR_MESSAGE: message.error(errorMessage); break; case ErrorShowType.NOTIFICATION: notification.open({ description: errorMessage, message: errorCode, }); break; case ErrorShowType.REDIRECT: // TODO: 重定向 break; default: message.error(errorMessage); } } } else if (error.response) { // Axios 错误 // 请求已发出,服务器响应了非 2xx 的状态码 message.error(`Response status:${error.response.status}`); } else if (error.request) { // 请求已发出但未收到响应 message.error('None response! Please retry.'); } else { // 请求设置阶段触发的错误 message.error('Request error, please retry.'); } }, }, // 请求拦截器 requestInterceptors: [ (config) => { // 拦截请求配置做个性化处理。 const url = config.url.concat('?token = 123'); return { ...config, url}; } ], // 响应拦截器 responseInterceptors: [ (response) => { // 拦截响应数据做个性化处理 const { data } = response; if(!data.success){ message.error('Request failed!'); } return response; } ] };这套方案覆盖了完整的错误分类:业务错误(errorThrower抛出BizError,按showType分五种展示级别)、HTTP 非 2xx 错误(error.response)、无响应错误(error.request)与请求设置错误。仓库中 examples/max/app.ts 与 examples/antd-pro-create/src/requestErrorConfig.ts 提供了该方案的真实落地版本,可直接对照参考。
小结:配置项速查
| 配置位置 | 配置项 | 作用 | 源码依据 |
|---|---|---|---|
| 构建配置 | request.dataField | useRequest数据解包字段,默认data,''表示原样返回 | request.ts |
src/app.ts | RequestConfig其余字段 | 透传给axios.create()全局生效 | request.ts |
src/app.ts | errorConfig.errorThrower | data.success === false时抛错 | request.ts |
src/app.ts | errorConfig.errorHandler | 统一捕获并处理错误,可用skipErrorHandler跳过 | request.ts |
src/app.ts/ 单次请求 | requestInterceptors/responseInterceptors | 全局 / 一次性拦截器,兼容 axios 与 umi-request 两种写法 | request.ts |
| 单次请求 | getResponse | true时返回完整AxiosResponse,否则返回data | request.ts |
掌握以上内容后,你可以独立完成:基于 axios 的统一请求初始化、按团队约定解包后端数据、按错误分级实现统一错误处理、用拦截器注入鉴权 token 等横切逻辑,并在 umi@3 存量项目中平滑迁移到 umi@4 的请求方案。
【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考