引言
在前面两篇文章中得到反馈效果不错,今天继续出第三篇,以
二次封装axios
为例
在我以往的面试中,听到候选人最多的就是项目中二次封装axios
,但是当真正深入挖掘,往往得不到有用的信息。 那么面试官是想听到什么样的亮点呢?这篇文章我们重点分析一下,并且也可以封装自己的axios
请求库。
需求
在项目中,我们可能存在这些痛点:
- 接口统一管理
- 支持多host问题
- 支持区分env
- 支持restful风格
- 支持取消请求
- 支持接口错误重试
- 支持缓存
- 支持限流
请求方法的统一封装
export class Apis{ public common: RequestOptions; // 默认的server配置 public base!: string; // server服务的集合 public serverMap: ServerMap; // 对象形式的请求方法集合 public apiMap: ApisMap; // 挂载所有请求方法的集合对象 public apis: ApisInstance; // axios实例化对象 public instance: AxiosInstance; constructor(common?: RequestOptions, serverMap?: ServerMap, apiMap?: ApisMap) { } public get<T extends Record<string, any> = any>(url: string, request: RequestOptions): Promise<RestyResponse<T>> { request = { ...request, method: 'GET' }; return this.request(url, request); } public delete<T extends Record<string, any> = any>(url: string, request: RequestOptions): Promise<RestyResponse<T>> { request = { ...request, method: 'DELETE' }; return this.request(url, request); } public post<T extends Record<string, any> = any>(url: string, request: RequestOptions): Promise<RestyResponse<T>> { request = { ...request, method: 'POST' }; return this.request(url, request); } public put<T extends Record<string, any> = any>(url: string, request: RequestOptions): Promise<RestyResponse<T>> { request = { ...request, method: 'PUT' }; return this.request(url, request); } public patch<T extends Record<string, any> = any>(url: string, request: RequestOptions): Promise<RestyResponse<T>> { request = { ...request, method: 'PATCH' }; return this.request(url, request); } public request<T extends Record<string, any> = any>(url: string, request: RequestOptions): Promise<RestyResponse<T>> { const rest = request.rest || {}; let path = url; if (Object.keys(rest).length) { path = this.restful(url, rest); } // 合并公共配置 const options = { ...this.common, ...request }; return this.instance.request({ ...options, url: path, }); } }
接口统一管理
在项目中,实际每个请求的写法都是一样的,开发过程中,我不想在每个页面都重复写请求方法,我想通过JSON配置的方式做接口统一管理,比如:
在Home Module下,新建apis.ts文件:
export default { getBaseInfo: { method: 'get', url: '/base/get' }, getBaseRestInfo: { method: 'get', url: '/base/info' } }
实现
:
import axios, { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios' import { ApisMap, ServerMap, ApisInstance, ApisConfig, ResolvedFn, RejectedFn, Middleware, Rest } from './types' class Apis { base: string serverMap: ServerMap apiMap: ApisMap instance: ApisInstance axiosInstance: AxiosInstance constructor(serverMap: ServerMap, apiMap: ApisMap, common?: AxiosRequestConfig) { /** * 支持公共配置 */ this.axiosInstance = axios.create(common) this.serverMap = serverMap this.apiMap = apiMap this.instance = {} this.base = this.getDefault() this.combine2Request() } /** * 获取默认的配置 */ getDefault(): string { let base = '' for (const key of Object.keys(this.serverMap)) { /** * 找到默认的配置值 */ if (this.serverMap[key].default) { base = key } } if (!base) { console.error('apis: 找不到默认服务器配置') } return base } combine2Request(): void { for (const key of Object.keys(this.apiMap)) { this.instance[key] = (config?: ApisConfig) => { let result: ApisConfig = this.apiMap[key] if (config) { result = this.rest2Combine(this.apiMap[key], config) } return this.axiosInstance.request(result) } } } } export default createInstance
支持多host
应用可能需要跟多个服务交互,这时候涉及多host,我们想统一处理:
- 定义serverMap:
serverMap
对象定义了两个服务:baseServer
和api-test
。- 对于每个服务,都提供了一个
baseMap
对象来描述不同环境下的base URLs。 baseServer
有一个default
属性设置为true
,表示它可能是默认选择的服务器。- 定义apiMap:
apiMap
对象定义了两个API:getBaseInfo
和getBaseRestInfo
。- 每个API都有一个HTTP方法(
method
)和一个URL路径(url
)。
import createInstance from 'apis' import { ApisMap } from 'apis/types' const serverMap = { baseServer: { baseMap: { localprod: '', prod: 'https://wwww.baidu.com', stage: 'https://wwww.baidu.com', test: 'https://wwww.baidu.com', dev: 'https:/wwww.baidu.com', local: 'http://127.0.0.1:4320', baseURL: 'https://localhost:8080' }, default: true }, 'api-test': { baseMap: { localprod: '', prod: 'https://www.baidu.com', stage: 'https://www.baidu.com', test: 'https://www.baidu.com', dev: 'https:/www.baidu.com', local: `http://127.0.0.1:4320`, baseURL: 'https://localhost:8080' } } } const apiMap: ApisMap = { getBaseInfo: { method: 'get', url: '/base/get' }, getBaseRestInfo: { method: 'get', url: '/base/get/:id/kill/:test' } } let apis = createInstance(serverMap, apiMap) apis.getBaseInfo({ params: { name: 'linwu' } }).then(res => { console.log(res) })
实现
/** * 给个请求 * 配置正确的baseURL * 如果没有baseURL就读默认的 */ formatConfigUrl(): void { for (const key of Object.keys(this.apiMap)) { const item = this.apiMap[key] if (!item.server) { item.server = this.base } this.apiMap[key] = { ...this.serverMap[item.server], ...item } } }