【纯血鸿蒙】之axios网络请求库终极封装

4,464 阅读4分钟

开始

上篇写了封装鸿蒙自带的http网络请求库,大量jy们在群里和评论区反馈说要封装个基于axios的,接下来我们一步一个脚印来实现和进行终极封装!

开发环境

  • Windows

  • DevEco Studio NEXT Developer Preview2

  • HarmonyOS next Developer Preview2

  • java version "11.0.18" 2023-01-17 LTS

  • hdc 1.2.0a

  • 手机:Mate 60Pro (HarmonyOS NEXT Developer Preview2)

导入axios库

"@ohos/axios": "^2.1.1",

探讨封装

接下来我们要封装axios,我们想到的是后续怎么拓展,比方说后面我不用axios了,有个更强大的网络库来支持,类似安卓早期xutils、Volley、okhttp,如果要替换相当麻烦,所有我们需要封装一个AxiosClient(用来处理封装axios核心代码)还有一个AxiosHttp(用来处理网络请求拦截、实现、暴露方法供上层调用),这样一来分工明确,直接上代码

封装泛型工具类

和http一样需要封装一个封装泛型工具类,代码很简单

export class Result<T> {
  code: number = 0
  msg: string = ""
  data: T | null = null 
}

封装AxiosClient

  • 接下来封装AxiosClient
/**
* axios请求客户端创建
*/
const axiosClient = new AxiosHttpRequest({
 baseURL: "/api",
 timeout: 10 * 1000,
 checkResultCode: false,
 headers: {
   'Content-Type': 'application/json'
 } as AxiosRequestHeaders,
 interceptorHooks: {
   requestInterceptor: async (config) => {
     // 在发送请求之前做一些处理,例如打印请求信息
     LogUtils.debug('网络请求Request 请求方法:', `${config.method}`);
     LogUtils.debug('网络请求Request 请求链接:', `${config.url}`);
     LogUtils.debug('网络请求Request Params:', `\n${JsonUtils.stringify(config.params)}`);
     LogUtils.debug('网络请求Request Data:', `${JsonUtils.stringify(config.data)}`);
     axiosClient.config.showLoading = config.showLoading
     if (config.showLoading) {
       showLoadingDialog("加载中...")
     }
     if (config.checkLoginState) {
       let hasLogin = await StorageUtils.get(StorageKeys.USER_LOGIN, false)
       LogUtils.debug('网络请求Request 登录状态校验>>>', `${hasLogin.toString()}`);
       if (hasLogin) {
         return config
       } else {
         if (config.needJumpToLogin) {
           Router.push(RoutePath.TestPage)
         }
         // throw new AxiosError("请登录","-100")
         throw new Error("请登录")
       }
     }
     return config;
   },
   requestInterceptorCatch: (err) => {
     LogUtils.error("网络请求RequestError", err.toString())
     if (axiosClient.config.showLoading) {
       hideLoadingDialog()
     }
     return err;
   },
   responseInterceptor: (response: AxiosResponse) => {
     //优先执行自己的请求响应拦截器,在执行通用请求request的
     if (axiosClient.config.showLoading) {
       hideLoadingDialog()
     }
     LogUtils.debug('网络请求响应Response:', `\n${JsonUtils.stringify(response.data)}`);
     if (response.status === 200) {
       let config = response.config as HttpRequestConfig
       const checkResultCode = config.checkResultCode
       if (checkResultCode && response.data.errorCode != 0) {
         showToast(response.data.errorMsg)
         return Promise.reject(response)
       }
       return Promise.resolve(response.data);
     } else {
       return Promise.reject(response);
     }
   },
   responseInterceptorCatch: (error) => {
     if (axiosClient.config.showLoading) {
       hideLoadingDialog()
     }
     LogUtils.error("网络请求响应异常", error.toString())
     errorHandler(error);
     return Promise.reject(error);
   },
 }
});
  • errorHandler处理,一些40开头的错误
export function errorHandler(error: CommonType) {

  if (error instanceof AxiosError) {
    switch (error.status) {
    // 401: 未登录
    // 未登录则跳转登录页面,并携带当前页面的路径
    // 在登录成功后返回当前页面,这一步需要在登录页操作。
      case 401:

        break;
    // 403 token过期
    // 登录过期对用户进行提示
    // 清除本地token和清空vuex中token对象
    // 跳转登录页面
      case 403:
        showToast("登录过期,请重新登录")
      // 清除token
      // localStorage.removeItem('token');
        break;
    // 404请求不存在
      case 404:
        showToast("网络请求不存在")
        break;

    // 其他错误,直接抛出错误提示
      default:
        showToast(error.message)
    }
  }
}

封装AxiosHttp

  • 由于api11不支持any了,这边定义了一个通用数据类型CommonType来代替
//通用数据类型
export type CommonType = number | string | boolean | Array<number> | Array<string> | Array<boolean> | object | Object | ArrayBuffer
  • 首先定义两个接口,用来处理响应码和拦截相关
interface InterceptorHooks {
requestInterceptor?: (config: HttpRequestConfig) => Promise<HttpRequestConfig>;
requestInterceptorCatch?: (error: CommonType) => CommonType;
responseInterceptor?: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
responseInterceptorCatch?: (error: CommonType) => CommonType;
}

export interface  HttpRequestConfig extends  InternalAxiosRequestConfig {
showLoading?: boolean; //是否展示请求loading
checkResultCode?: boolean; //是否检验响应结果码
checkLoginState?: boolean //校验用户登陆状态
needJumpToLogin?: boolean //是否需要跳转到登陆页面
interceptorHooks?: InterceptorHooks
}
  • 接下来网络请求构造和接口封装
class AxiosHttpRequest {
config: HttpRequestConfig;
interceptorHooks?: InterceptorHooks;
instance: AxiosInstance;

constructor(options: HttpRequestConfig) {
 this.config = options;
 this.interceptorHooks = options.interceptorHooks;
 this.instance = axios.create(options);
 this.setupInterceptor()
}

setupInterceptor(): void {
 this.instance.interceptors.request.use(//这里主要是高版本的axios中设置拦截器的时候里面的Config属性必须是InternalAxiosRequestConfig,但是InternalAxiosRequestConfig里面的headers是必传,所以在实现的子类我设置成非必传会报错,加了个忽略注解
   this.interceptorHooks?.requestInterceptor,
   this.interceptorHooks?.requestInterceptorCatch,
 );
 this.instance.interceptors.response.use(
   this.interceptorHooks?.responseInterceptor,
   this.interceptorHooks?.responseInterceptorCatch,
 );
}

// 类型参数的作用,T决定AxiosResponse实例中data的类型
request<T = CommonType>(config: HttpRequestConfig): Promise<T> {
 return new Promise<T>((resolve, reject) => {
   this.instance
     .request<CommonType, T>(config)
     .then(res => {
       resolve(res);
     })
     .catch((err: CommonType) => {
       LogUtils.error("网络请求Request异常:", err.toString())
       errorHandler(err)
       if (err) {
         reject(err);
       }
     });
 });
}

get<T = CommonType>(config: HttpRequestConfig): Promise<T> {
 config.method = 'GET'
 return this.request(config);
}

post<T = CommonType>(config: HttpRequestConfig): Promise<T> {
 config.method = 'POST'
 return this.request(config);
}

delete<T = CommonType>(config: HttpRequestConfig): Promise<T> {
 config.method = 'DELETE'
 return this.request(config);
}

patch<T = CommonType>(config: HttpRequestConfig): Promise<T> {
 config.method = 'PATCH'
 return this.request(config);
}
}

使用

  • 使用方式两种1、包一个Result,用来判断code码 2、直接返回数据
  • commonHeader是自定义的
  • 其中axios有个params和data传参,params是类似于安卓的query方式,data是类似于安卓的body方式
const commonHeader: AxiosHeaders = new AxiosHeaders()
/** 
*  直接返回数据TestModel
*  @returns
*/
export function getTestListAxios(date: string = "") {
return axiosClient.get<TestModel>({
 url: baseUrl + "test",
 params: { "date": date },
 showLoading: true,
 headers: commonHeader
})
}
/**
* 测试校验API data:T泛型数据
* @returns
*/
export function getTest() {
return axiosClient.get<Result<Test[]>>(
 {
   url:  baseUrl + "test",
   checkResultCode: true,
   showLoading: true,
   headers: commonHeader
 }
)
}

总结

  • 封装泛型工具类
  • 封装AxiosClient
  • 封装AxiosHttp
  • 最后直接使用
  • Axios有个好处就是不用解析,底层解析了,直接可以用想要的数据

下集预告

下一篇我们封装一个事件总线工具,类型安卓的eventBus用来刷新页面组件和传参

本文正在参加华为鸿蒙有奖征文征文活动