背景
项目为旧单体项目, 改造为微前端项目。所以原项目存在大量共享工具及组件。当前方案是将这些共享代码根据功能,拆分为第三方包。这其中就包括http请求对象。而后端API并不参入前端业务的拆分,所以我们需要保证子应用与基座使用相同的请求配置。 这里记录相关的解决思路。
目标
我们希望子应用存在独立开发和嵌入基座的能力。
- 独立开发: 使用本地请求对象
- 嵌入基座:使用基座请求对象
目录
- pkgs
- http 公共请求封装
- api 可公用的特定请求函数
这里我们将分为两个独立的包, http负责最基础的业务请求对象封装,例如登录拦截, token设置,接口兼容等, api 负责具体的业务请求,提供公共的请求方法。例如: 用户信息查询, 状态查询等.
http 包
目录
- http
- src
- core.ts 请求封装
- transform-res.ts 响应拦截
- transform-req.ts 请求拦截
- utils.ts 工具函数
- index.ts 入口
- src
核心
代码语言:javascript复制import { AxiosInstance, AxiosRequestConfig } from 'axios'
import qs from 'query-string'
import { deepCopy, getParams } from './utils'
const DEFAULT_CONF:AxiosRequestConfig = {
timeout: 1000 * 6,
withCredentials: true,
headers: {
'Content-Type': 'application/json; charset=utf-8'
},
baseURL: process.env.NODE ? '/DEV_PROXYY' : '',
paramsSerializer
}
export interface iTimetamp{
T:number
[props:string]:any
}
/**
* 刷新请求
* @summary 通过添加多余的时间戳参数,防止浏览器缓存,刷新请求。
*/
export function paramsSerializer<T extends iTimetamp>(params:T):string{
params = params.T ? { ...params, T: new Date().getTime() } : params
return qs.stringify(params)
}
/**
* 默认配置
*/
export function getDefaultConf():AxiosRequestConfig{
return deepCopy<AxiosRequestConfig>(DEFAULT_CONF)
}
/**
* 请求对象
* @summary
* 使用单例模式,全局提供唯一请求对象
* 封装常用请求方式
* @props
* - _axios axios实例
* - CONF 当前全局配置
* @maths
* - GET
* - POST
* - PUT
*
* - addResInterceptors response 拦截器
* - addReqInterceptors request 拦截器
*/
export class MicroHttp {
static _http:MicroHttp
_axios:AxiosInstance = {} as AxiosInstance // 单例兼容写法
interceptors:AxiosInstance['interceptors'] = {} as AxiosInstance['interceptors']
constructor(axiosObj:AxiosInstance){
if(MicroHttp._http){
return MicroHttp._http
}
this._axios = axiosObj
this.interceptors = this._axios.interceptors
MicroHttp._http = this
}
/**
* 二次封装请求接口
* 因为会使用中间件对返回数据做解包
* 所以方法返回的类型直接使用了 【泛型T】而不是原方法默认的 【AxiosResponse<T>】
*/
GET<Q, T>(url:string|[string, Q], conf?:AxiosRequestConfig):Promise<T>{
const [_url, _conf] = getParams(url, conf)
return MicroHttp._http._axios.get<T, T>(_url, _conf)
}
POST<Q, T>(url:string|[string, Q], data?:any, conf?:AxiosRequestConfig):Promise<T>{
[url, conf] = getParams(url, conf)
return MicroHttp._http._axios.post<T, T>(url, data, conf)
}
PUT<Q, T>(url:string|[string, Q], data?:any, conf?:AxiosRequestConfig):Promise<T>{
[url, conf] = getParams(url, conf)
return MicroHttp._http._axios.put<T, T>(url, data, conf)
这里重点是通过二次封装,提供了一个单例请求对象。
拦截器
这里的拦截器都是针对当前业务的特例处理函数, 之所以以单一函数插件的形式导出,是希望尽量降低底层包与业务的相关性
代码语言:javascript复制// 请求拦截
// 设置请求头 token
export function setToken(getToken:() => string):AxiosTransformer{
return (data:any, headers:any) => {
headers.token = getToken()
return JSON.stringify(data)
}
}
// 响应拦截
// 因原请求接口返回数据存在格式不统一的请求,所以响应拦截的主要目的是统一响应数据格式
export class ReqData<T>{
constructor(public _status:number, public _message:string, public data?:T) {
this._status = _status
this._message = _message
this.data = data
}
}
export interface IresData{
code?:number
status?:string
message?:string
[key:string]:any
}
export function resParse(response:string):IresData{
// 存在请求成功后无返回
try {
return response ? JSON.parse(response) as IresData : {}
} catch (error) {
//适配 直接返回状态字符?
console.error(`JSON 解析错误: ${error} => ${response}`)
return {_message: typeof response === 'string' ? response : ''}
}
}
....
其实单一的http,基本能满足基座与子应用请求对象的一致性。因为使用了单例模式, 子应嵌入基座时,基座与子应用使用同一依赖包,new MicroHttp(conf) 将返回同一请求对象。 也不需要通过传递
请求对象
保证配置的一致性
API 包
这个包主要提供多个应用或组件通用的请求函数, 是对http包的再次封装,与业务又强相关性。
目录
- api
- src
- http.ts
- index.ts
- api-1
- api-2
- .....
- src
http.ts
代码语言:javascript复制import {
MicroHttp,
resAdapter,
resListAdapter,
setToken,
getDefaultConf,
resParse,
resErr,
resServerErr
} from '@micro/http'
import cookies from 'js-cookie'
const inspect = Symbol.for('nodejs.util.inspect.custom');
const conf = {
...getDefaultConf(),
baseURL: process.env.NODE_ENV !== 'production' ? '/proxyApi/' : '/',
transformResponse: [
resParse,
resAdapter,
resListAdapter
],
transformRequest: [
setToken(() => {
return cookies.get('token') || ''
})
]
}
let _http:MicroHttp
const HTTP = new Proxy({} as MicroHttp, {
get(_, propKey:keyof MicroHttp | typeof inspect){
if(propKey === inspect) {
return `HTTP Proxy`
}
if (!_http) {
throw new Error('调用 HTTP,需要先使用 createHTTP 或 setHTTP 初始化请求对象')
}
return _http[propKey]
},
set(_, propKey:keyof MicroHttp, value:any){
_http[propKey] = value
return true
}
})
function setHTTP(http:MicroHttp):void{
_http = http
}
function createHTTP(axios: any): void {
setHTTP(new MicroHttp(axios.create(conf)))
HTTP.interceptors.response.use(resErr, resServerErr)
}
function getHTTP():MicroHttp {
return _http
}
export {
HTTP,
createHTTP,
setHTTP,
g
使用
代码语言:javascript复制import { createHTTP, HTTP } from '@micro/api'
import Axios from 'axios'
createHTTP(Axios)
HTTP.GET()