比较喜欢链式调用的方式,需要设置什么参数,调用具体的方法,视觉和逻辑上简洁明了。实现一个基于axios的网络请求封装,实现链式调用的效果
1、使用方式
//调用方式
HttpHelper.newClient<DataBean>()
.host("https://www.wanandroid.com")//可以全局定义域名,也可以后续传入,可用于多域名的情况
.api("article/list/0/json")//接口路径
.enableShowLoading(true)//是否显示加载弹窗
.enableShowToast(true)//是否toast显示请求错误信息
.params(Object({ "cid": 60 }))//传入参数
.onSuccess((info) => {
//成功回调,如果不需要可以不写
data(info)
})
.onError((error) => {
//错误回调,如果不需要可以不写
})
.get()//.post() .delete() put()
//使用中一般只需要这样,
class TestViewModel {
getListData2(data: (data?: DataBean) => void) {
HttpHelper.newClient<DataBean>()
.api("article/list/0/json")
.params(Object({ "cid": 60 }))
.onSuccess((info) => {
data(info)
})
.get()////.post() .delete() put()
}
}
2、实现方法
定义后端返回的数据格式,按需定义
//服务端返回的数据格式
export interface BaseApiResponse<T> {
errorCode?: number|string
errorMsg?: string
data?: T
}
//封装一个错误信息对象
export interface BaseError {
message: string;
code?: number | string;
}
实现类
/**
* 链式调用实现网络请求
*/
export class HttpHelper<T> {
private baseUrl: string = "https://www.wanandroid.com" //请求域名,默认放置
private timeOut: number = 10000 //超时时间
private requestApi?: string //接口路径
private paramsData?: object //参数
private isShowLoadingDialog: boolean = true//是否显示加载中弹窗
private isShowErrorToast: boolean = true//是否自动显示错误信息
private axiosInstance?: AxiosInstance; // axios 实例
private success?: (result?: T) => void;//请求成功返回正确数据的回调
private error?: (result?: BaseError) => void;//请求失败的回调
//链式调用,禁掉手动new对象
private constructor() {
}
// axios 请求配置
private defaultConfig: AxiosRequestConfig = {
baseURL: this.baseUrl,
timeout: this.timeOut
}
/**
* 动态设置域名
* @param host
* @returns
*/
host(host: string): HttpHelper<T> {
this.baseUrl = host
return this;
}
/**
* 接口路径
* @param api
* @returns
*/
api(api?: string): HttpHelper<T> {
this.requestApi = api
return this
}
/**
* 请求参数
* @param params
* @returns
*/
params(params?: object): HttpHelper<T> {
this.paramsData = params
return this
}
/**
* 创建对象
* @returns
*/
public static newClient<T>(): HttpHelper<T> {
return new HttpHelper()
}
/**
* 外部传入axios 配置,好像用不到,后续如果需要配置,仍是使用封装类增加相关配置
* @param config
* @returns
*/
// config(config: AxiosRequestConfig): HttpHelper<T> {
// this.defaultConfig = config
// return this
// }
/**
* 使用get方法
*/
get() {
this.request(RequestType.GET)
}
/**
* 使用post方法
*/
post() {
this.request(RequestType.POST)
}
/**
* 使用put方法
*/
put() {
this.request(RequestType.PUT)
}
/**
* 使用delete方法
*/
delete() {
this.request(RequestType.DELETE)
}
/**
* 传入成功回调函数
* @param success
* @returns
*/
onSuccess(success?: (result?: T) => void): HttpHelper<T> {
this.success = success;
return this;
}
/**
* 传入请求错误回调
* @param error
* @returns
*/
onError(error?: (result?: BaseError) => void): HttpHelper<T> {
this.error = error;
return this;
}
/**
* 设置是否允许显示加载中弹窗
* @param enableLoading
* @returns
*/
enableShowLoading(enableLoading: boolean): HttpHelper<T> {
this.isShowLoadingDialog = enableLoading;
return this;
}
/**
* 设置是否允许显示错误信息toast
* @param enableLoading
* @returns
*/
enableShowToast(enable: boolean): HttpHelper<T> {
this.isShowErrorToast = enable
return this
}
/**
* 在发起请求方法前,更新下配置信息
*/
private updateBaseConfig() {
this.defaultConfig.baseURL = this.baseUrl
this.defaultConfig.timeout = this.timeOut
this.axiosInstance = axios.create(this.defaultConfig);
this.initIntercept()
}
/**
* 设置拦截器信息,需要添加token,可以在这边全局设置
*/
private initIntercept() {
// 添加请求拦截器
this.axiosInstance?.interceptors.request.use((config: InternalAxiosRequestConfig) => {
let info = `请求路径 ${config.baseURL}${config.url}\n请求参数 ${JSON.stringify(config.params)}`
LogUtil.info(info)
return config;
}, (error: AxiosError) => {
return Promise.reject(error);
});
}
private showLoading() {
if (this.isShowLoadingDialog) {
LoadingDialog.getInstance().show()
}
}
private hideLoading() {
if (this.isShowLoadingDialog) {
LoadingDialog.getInstance().hide()
}
}
private showToast(message?: string) {
if (this.isShowErrorToast && message) {
ToastUtil.showShort(message)
}
}
/**
* 请求接口具体实现
* @param type
*/
private request(type: RequestType) {
this.updateBaseConfig()
this.showLoading()
let response: Promise<BaseResponse<BaseApiResponse<T>>>
switch (type) {
case RequestType.GET:
response = this.axiosInstance!.get<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
case RequestType.POST:
response = this.axiosInstance!.post<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
case RequestType.PUT:
response = this.axiosInstance!.put<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
case RequestType.DELETE:
response = this.axiosInstance!.delete<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
default:
response = this.axiosInstance!.get<string, BaseResponse<BaseApiResponse<T>>>(this.requestApi,
{ params: this.paramsData })
break
}
response.then((res) => {
let data = res.data
//获取到的数据无法像java那样直接映射成具体的数据对象类型,就算强转类型也无法使用对象中的方法
if (data.errorCode == 0) {
//请求成功获取到预期数据
if (this.success) {
this.success(data.data)
}
} else {
//请求成功,但是是返回服务器错误信息
if (this.error) {
let errBean: BaseError = {
code: data.errorCode,
message: data.errorMsg ?? ""
}
this.showToast(errBean.message)
if (this.error) {
this.error(errBean)
}
}
}
}).catch((error: AxiosError) => {
//请求失败,如网络无法连接
let errBean: BaseError = {
code: error.code,
message: error.message
}
this.showToast(errBean.message)
if (this.error) {
this.error(errBean)
}
}).finally(() => {
this.hideLoading()
});
}
}
3、问题
当后端返回的数据类型不统一时可能无法正常使用,比如:
{
code:1
message:""
data:{...}
}
{
errorCode:1
errorMsg:""
datas:{...}
}
封装实现了分割请求成功和失败的两种情况,避免了各个接口请求的公共逻辑代码(如弹窗、错误信息显示),对于拿到手的数据可以更专注于请求成功后的逻辑处理,但是这样请求的数据格式在框架中就限制死了
let response: Promise<BaseResponse<BaseApiResponse<T>>>
如果拿到了不是这个格式的数据,那么后续的逻辑处理都可能出现问题。
尝试过把BaseApiResponse整个当做HttpHelper的泛型传入,打算通过封装BaseApiResponse的子类来解决格式不统一的问题,但是,由于语言限制,好像鸿蒙中无法像android中json和对象互相映射的功能,android中可以创建多种数据格式的子类,利用多态特性,关系映射解决掉这个问题,但是鸿蒙好像还不行
华为官方关于Json解析成对象的相关说明 ,使用第三方映射库还是得传入实际的对象类型,暂时是想不到怎么在底层拿到上层的数据类型。
暂时没有比较完美的解决方式,本地端能处理的估计就是在拿到数据后如果已知数据格式不统一,在拦截器中修改成需要的数据类型。
不知道有没有大佬看到,希望不吝赐教。