Umi 请求方案深度解析:@umijs/max 基于 axios 与 useRequest 的统一请求与错误处理体系
Umi 请求方案深度解析umijs/max 基于 axios 与 useRequest 的统一请求与错误处理体系【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umiumijs/max内置了一套统一的网络请求与错误处理插件底层基于 axios 封装了request方法同时集成 ahooksahooksjs/use-request的useRequestHook并围绕运行时配置、拦截器和errorConfig提供完整的可定制体系。读完本文你将掌握dataField数据消费机制、运行时配置项errorConfig/requestInterceptors/responseInterceptors的完整用法、request与useRequest的 API 细节、请求取消方式以及 umi3umi-request迁移到 umi4axios时的关键差异并能对照插件源码理解每个配置项的落地实现。插件概览一个入口两种消费方式插件对外暴露request方法与useRequestHook 两个核心 APIimport { request, useRequest } from umi; request; useRequest;从源码结构看该插件位于 packages/plugins/src/request.ts通过api.describe声明配置 key 为request并调用api.addRuntimePluginKey(() [request])注册运行时插件键——这正是你可以在src/app.ts中导出request运行时配置的底层原因。插件在api.onGenerateFiles阶段根据模板渲染生成/core/request.ts其中通过getPluginManager().applyPlugins({ key: request, type: ApplyPluginsType.modify })从插件管理器中取出你在src/app.ts导出的运行时配置再注入生成的请求实现中。仓库中提供了一个可直接参考的示例工程 examples/with-request其中 examples/with-request/app.ts 演示了运行时配置写法examples/with-request/pages/index.tsx 演示了页面内调用request(/api/users)配合 examples/with-request/mock/users.ts 中的 mock 数据{ success: true, data: [sorrycc, chencheng] }恰好可以验证后文要讲的dataField机制。配置构建时配置dataField构建时配置的核心只有一个字段dataFieldexport default { request: { dataField: data }, };该配置的默认值是data主要目的是让useRequest直接消费“解包后”的业务数据。如果你希望拿到后端的原始数据需要将其配置为。假设你的后端返回如下格式{ success: true, data: 123, code: 1, }那么useRequest就可以直接消费data字段其值为123而不是整个{ success, data, code }对象。源码中这一机制的落地非常直白见 packages/plugins/src/request.tslet dataField api.config.request?.dataField; if (dataField undefined) dataField data; const isEmpty dataField ; const formatResult isEmpty ? result result : result result?.${dataField};也就是说构建时插件会生成一个formatResult函数注入到useRequest中未配置时默认执行result result?.data配置为时退化为恒等函数result result原样返回响应体。同时模板会根据dataField生成带[${dataField}]索引的类型重载保证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三项插件专属配置外其余配置timeout、headers、baseURL等都会直接透传给 axios 的 request config——源码中getRequestInstance直接将运行时配置作为参数执行axios.create(config)。这些规则将应用于项目中所有的request与useRequest调用因为二者共享同一个惰性创建的 axios 单例实例。errorConfig如果你需要统一的错误处理方案可以在此配置。其设计为“抛出—处理”两段式errorThrower接收后端返回的数据负责根据业务约定抛出你自定义的 error。注意它的触发条件是响应体中data.success false。从源码看它实际上是被注册为最后一个响应拦截器实现的// 当响应的数据 success 是 false 的时候抛出 error 以供 errorHandler 处理。 requestInstance.interceptors.response.use((response) { const { data } response; if(data?.success false config?.errorConfig?.errorThrower){ config.errorConfig.errorThrower(data); } return response; })errorHandlerrequest会 catch 到errorThrower抛出的错误并调用该方法。按类型定义它接收两个参数catch 到的 error 和本次请求的 opts从源码的实际调用看运行时还会把全局运行时配置作为第三个参数一并传入。errorHandler与errorThrower需要配套使用完整的配套示例见文末“运行时配置示例”。如果嫌这套机制繁琐你也可以完全跳过errorConfig直接在响应拦截器里实现自己的错误处理。requestInterceptors为request添加请求阶段的拦截器。传入数组每个元素按顺序依次注册到 axios 实例上写法与 axios request interceptor 一致接收 request config 并返回。源码中还额外兼容了 umi-request 风格的拦截器——通过函数 arity 判断requestInstance.interceptors.request.use(async (config) { const { url } config; if(interceptor.length 2){ // umi-request 风格(url, options) { url, options } const { url: newUrl, options } await interceptor0; return { ...options, url: newUrl }; } // axios 风格(config) config return interceptor[0](https://link.gitcode.com/i/5358707bb696c70344291da6f7d1b8cb); })因此每个拦截器元素支持三种写法建议配合RequestConfig类型规范书写const request: RequestConfig { requestInterceptors: [ // 1. 直接写一个 function作为拦截器 (url, options) { // do something return { url, options } }, // 2. 二元组第一个元素是 request 拦截器第二个元素是错误处理 [(url, options) {return { url, options }}, (error) {return Promise.reject(error)}], // 3. 数组形式省略错误处理 [(url, options) {return { url, options }}] ] }值得注意的是虽然项目兼容 umi-request 的拦截器写法但这种写法无法通过 TypeScript 的语法检查。responseInterceptors为request添加响应阶段的拦截器。同样传入数组并按顺序注册拦截器接收 axios 的 response 作为参数并返回const request: RequestConfig { responseInterceptors: [ // 直接写一个 function作为拦截器 (response) { // 不再需要异步处理读取返回体内容可直接在data中读出部分字段可在 config 中找到 const { data {} as any, config } response; // do something return response }, // 一个二元组第一个元素是响应拦截器第二个元素是错误处理 [(response) {return response}, (error) {return Promise.reject(error)}], // 数组省略错误处理 [(response) {return response}] ] }注意拦截器按你的数组顺序依次注册但执行顺序遵循 axios 的规则——request 拦截器是后添加的在前response 拦截器是后添加的在后。仓库示例 examples/with-request/app.ts 中三个请求拦截器以 3、2、1 的数组顺序注册其console.log实际打印顺序为 1、2、3直观印证了“后添加的在前”这一执行顺序。APIuseRequest插件内置了ahooksjs/use-request你可以在组件内通过该 Hook 便捷地消费数据import { useRequest } from umi; export default function Page() { const { data, error, loading } useRequest(() { return services.getUserList(/api/test); }); if (loading) { return divloading.../div; } if (error) { return div{error.message}/div; } return div{data.name}/div; };注意上面的data并不是后端返回的原始数据而是其内部按dataField解包后的值因为构建时配置默认是data。以 examples/with-request/mock/users.ts 的 mock 数据为例useRequest得到的data将是[sorrycc, chencheng]而不是{ success: true, data: [...] }。另外需要注意ahooks 已更新到 3.0而为了降低umi3项目升级的难度插件继续沿用了 ahooks 2.0 版本的useRequest。request通过import { request } from /plugin-request或从umi/umijs/max导入即可使用内置的请求方法。除透传 axios 的所有 config 外插件额外提供了四个属性request(/api/user, { params: { name : 1 }, timeout: 2000, // other axios options skipErrorHandler: true, getResponse: false, requestInterceptors: [], responseInterceptors: [], } )skipErrorHandler将某个请求设置为true时可在errorHandler中识别并跳过统一错误处理如示例中if (opts?.skipErrorHandler) throw error;。getResponserequest默认只返回后端数据res.data传入{ getResponse: true }可拿到 axios 完整的 response 结构。源码中对应resolve(getResponse ? res : res.data)且类型层通过IRequestOptionsWithResponse/IRequestOptionsWithoutResponse的条件类型重载保证返回值类型正确。requestInterceptors/responseInterceptors写法与运行时配置相同但在这里注册的拦截器是一次性的。从源码看每次调用request时动态注册这些拦截器并在请求then/catch两个分支中都执行eject卸载确保不会泄漏到下一次请求。此外这些请求级拦截器会在运行时配置的拦截器之后被注册。注意当你使用了errorHandler时在这里注册的 response 拦截器会失效因为在errorHandler阶段就已经 throw error 了。RequestConfig这是一个接口定义帮助你规范地书写运行时配置import type { RequestConfig } from umi; export const request: RequestConfig {};注意导入时要加type。源码中该接口继承自AxiosRequestConfig扩展了errorConfig、requestInterceptors、responseInterceptors字段见 packages/plugins/src/request.ts 中的RequestConfig定义这也是为什么前文强调“其余配置都透传 axios”。取消请求使用 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 实例无需插件额外支持。umi3 到 umi4 的迁移要点在umi3到umi4的升级中官方弃用了 umi-request改选 axios 作为默认请求方案相关行为也发生了变化。运行时配置的变动export const request: RequestConfig { errorConfig: { errorHandler: () {}, errorThrower: () {} -- errorPage: , -- adaptor: (){}, }; -- middlewares: [], requestInterceptors: [], responseInterceptors: [], ... // umi-request 和 axios 的区别。 };umi-request 的配置项变成了 axios 的配置项去除了middlewares中间件你可以使用 axios 的拦截器实现相同的功能errorConfig删除了原有配置errorPage、adaptor等新增errorHandler和errorThrower来进行统一错误处理的设定。中间件的替换规则next()之前的逻辑放入requestInterceptorsnext()之后的逻辑放入responseInterceptors// 中间件umi3 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 } } // 拦截器umi4 { requestInterceptors:[ (config) { if (config.url.indexOf(/api) ! 0) { config.url /api/v1/${url}; } return config; } ], responseInterceptors: [ (response) { if(!response.data.success){ // do something } } ] }request 方法的参数变动umi-request 与 axios 的配置项存在一定区别如params、data的语义、错误对象结构等迁移时需要逐一对比二者官方文档调整调用参数。GET 请求参数序列化Umi3umi-request默认用相同的 Key 序列化数组Umi4 基于 axios默认是带括号[]的形式// Umi3 import { useRequest } from umi; // a: [1,2,3] a1a2a3 // Umi4 import { useRequest } from umijs/max; // a: [1,2,3] a[]1a[]2a[]3如果后端仍期望 Umi3 的格式可以在运行时配置中自定义paramsSerializer// src/app.[ts|tsx] import queryString from query-string; export const request: RequestConfig { paramsSerializer(params) { return queryString.stringify(params); }, ... }paramsSerializer同为 axios 标准配置项经运行时配置透传到 axios 实例即可生效。运行时配置完整示例下面给出一个完整的运行时配置示例帮助你为自己的项目设定个性化的请求方案错误处理方案沿用 umi3 的内置方案方便平滑迁移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}, // 错误处理 umi3 的错误处理方案。 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: // do nothing 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: redirect break; default: message.error(errorMessage); } } } else if (error.response) { // Axios 的错误 // 请求成功发出且服务器也响应了状态码但状态代码超出了 2xx 的范围 message.error(Response status:${error.response.status}); } else if (error.request) { // 请求已经成功发起但没有收到响应 // error.request 在浏览器中是 XMLHttpRequest 的实例 // 而在node.js中是 http.ClientRequest 的实例 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(请求失败); } return response; } ] };这套错误处理方案来自 umi3 的内置实现在 umi4 中官方将其移出内置、交由用户自行定制以获得更大的自由度——如果你仍想沿用可直接将该配置粘贴到项目中。除errorConfig外你也可以完全通过编写响应拦截器来实现自己的错误处理二者并不冲突、也不局限于errorConfig一种路径。小结umijs/max的请求插件用“构建时dataField解包 运行时RequestConfig统一配置 axios 拦截器体系 errorConfig错误兜底”四层机制覆盖了从数据消费、请求改写、响应处理到错误展示的全链路诉求。理解 packages/plugins/src/request.ts 中的配置解析与拦截器注册逻辑、参考 examples/with-request 示例工程可以让你在面对后端协议变更或从 umi3 迁移时快速定位并调整每一层的行为。【免费下载链接】umiA framework in react community ✨项目地址: https://gitcode.com/GitHub_Trending/um/umi创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考