HarmonyOS NEXT应用开发实战之Axios网络库的封装和使用

在现代应用开发中,网络请求是不可或缺的一部分。Axios库非常流行,而在HarmonyOS NEXT中,Axios库依然可用,可使用Axios库进行网络请求,可以极大地简化开发者的工作。通过对Axios的封装,不仅提升了代码的可维护性和可读性,还提供了一系列便捷的功能。本文将介绍Axios网络库的封装及其优点,帮助开发者在HarmonyOS NEXT环境下实现高效的网络请求。

axios三方库介绍

@ohos/axios是基于Axios原库v1.3.4版本为HarmonyOS/OpenHarmony适配的三方库,是一个基于promise的网络请求库,可以运行node.js和浏览器中,沿用其现有用法和特性。

 

http请求
Promise API
requestresponse拦截器
转换requestresponsedata数据
自动转换JSON data数据 

 

 axios三方库安装

在项目的根目录下,命令行执行以下命令即可:

ohpm install @ohos/axios

 axios三方库封装的意义

axios进行封装的意义在于提供更高层次的抽象,以便简化网络请求的使用和管理。以下是一些具体的理由

 

1.统一接口:封装后,可以统一管理所有的网络请求接口,使得在应用中调用网络请求时更加一致,减少重复代码

 

2.简化配置:封装可以避免每次请求都需要重复配置相似的参数(例如headers、请求方式等),通过配置对象直接传入更简洁

 

3.请求和响应拦截器:封装允许在发送请求之前或收到响应之后,对请求或响应进行处理,比如添加公共的请求头、处理错误、数据格式化等

 

4.错误处理:通过自定义的错误处理机制,可以实现统一的错误处理逻辑,比如根据状态码处理特定的错误(例如401未登录、403权限不足等)

 

5.增强功能:可以根据项目需求添加额外的功能,例如显示加载状态、处理用户登录状态等

 

6.提高可维护性:将网络请求相关的逻辑集中管理,可以让代码更加清晰,降低维护成本

 

7.支持特定业务需求:可根据实际的业务需求扩展功能,比如提供缓存机制、重试机制等,增强请求的灵活性。

 

封装后的使用效果

 

//axiosClient.ets
//author:csdn猫哥(blog.csdn.net/qq8864)

import {axiosClient,HttpPromise} from '../../utils/axiosClient';
import { ZhiNewsRespData,ZhiDetailRespData, HotMovieReq, MovieRespData } from '../bean/ApiTypes';



// 获取知乎列表页api接口
export const getZhiHuNews = (date:string): HttpPromise<ZhiNewsRespData> => axiosClient.get({url:'/zhihunews/'+date});

// 获取知乎详情页api接口
export const getZhiHuDetail = (id:string): HttpPromise<ZhiDetailRespData> => axiosClient.get({url:'/zhihudetail/'+id});

// 获取热门影视接口,仅作为post的使用示例,未使用
export const getHotMovie = (req:HotMovieReq): HttpPromise<MovieRespData> => axiosClient.post({url:'/hotmovie',data:req});


// 使用举例:
/*
 getHotMovie({start:1,count:2,city:'郑州'}).then((res) => {
      Log.debug(res.data.message)
      Log.debug("request","res.data.code:%{public}d",res.data.code)
    }).catch((err: BusinessError) => {
      Log.debug("request","err.data.code:%d",err.code)
      Log.debug("request",err.message)
    });
 */

可以看出,封装后接口的使用清晰简单直观,一行代码一个接口,风格统一,参数简单整洁。

封装的实现过程

//axiosClient.ets
//author:csdn猫哥(blog.csdn.net/qq8864)
import axios, { AxiosError, AxiosInstance, AxiosRequestHeaders, AxiosResponse, InternalAxiosRequestConfig } from "@ohos/axios";

interface HttpResponse<T> {
  data: T;
  status: number;
  statusText: string;
  config: HttpRequestConfig;
}

export type HttpPromise<T> = Promise<HttpResponse<T>>;

interface InterceptorHooks {
  requestInterceptor?: (config: HttpRequestConfig) => Promise<HttpRequestConfig>;
  requestInterceptorCatch?: (error: any) => any;
  responseInterceptor?: (response: AxiosResponse) => AxiosResponse | Promise<AxiosResponse>;
  responseInterceptorCatch?: (error: any) => any;
}

// @ts-ignore
interface HttpRequestConfig extends InternalAxiosRequestConfig {
  showLoading?: boolean; // 是否展示请求loading
  checkResultCode?: boolean; // 是否检验响应结果码
  checkLoginState?: boolean; // 校验用户登陆状态
  needJumpToLogin?: boolean; // 是否需要跳转到登陆页面
  interceptorHooks?: InterceptorHooks; // 拦截器
  headers?: AxiosRequestHeaders;
  errorHandler?: (error: any) => void; // 错误处理
}

export 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(
      this.interceptorHooks?.requestInterceptor,
      this.interceptorHooks?.requestInterceptorCatch,
    );
    this.instance.interceptors.response.use(
      this.interceptorHooks?.responseInterceptor,
      this.interceptorHooks?.responseInterceptorCatch,
    );
  }

  request<T = any>(config: HttpRequestConfig): HttpPromise<T> {
    return new Promise<HttpResponse<T>>((resolve, reject) => {
      this.instance.request<any, HttpResponse<T>>(config)
        .then(res => {
          resolve(res);
        })
        .catch(err => {
          const errorHandler = config.errorHandler || errorHandlerDefault;
          errorHandler(err);
          reject(err);
        });
    });
  }

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

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

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

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

function errorHandlerDefault(error: any) {
  if (error instanceof AxiosError) {
    // 处理Axios的错误
  }
  // 处理其他类型的错误
}

export default AxiosHttpRequest;

 如何使用?

 上述封装仅是统一了接口访问风格,但还不方便统一使用,可以再建一个全局单例的工具类使用:

//axiosClient.ets
//author:csdn猫哥(blog.csdn.net/qq8864)
import {AxiosHttpRequest,HttpPromise} from './axiosHttp'
import {AxiosRequestHeaders,AxiosError } from '@ohos/axios';
import { Log } from './logutil';
import { promptAction } from "@kit.ArkUI";

function showToast(msg:string){
  Log.debug(msg)
  promptAction.showToast({ message: msg })
}

function showLoadingDialog(msg:string){
  Log.debug(msg)
  promptAction.showToast({ message: msg })
}

function hideLoadingDialog() {

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

function errorHandler(error: any) {
  if (error instanceof AxiosError) {
    //showToast(error.message)
  } else if (error != undefined && error.response != undefined && error.response.status) {
    switch (error.response.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.response.data.message)
    }

  }
}

export  {axiosClient,HttpPromise};

封装后的使用

import {axiosClient,HttpPromise} from '../../utils/axiosClient';
import { ZhiNewsRespData,ZhiDetailRespData, HotMovieReq, MovieRespData } from '../bean/ApiTypes';



// 获取知乎列表页api接口
export const getZhiHuNews = (date:string): HttpPromise<ZhiNewsRespData> => axiosClient.get({url:'/zhihunews/'+date});

// 获取知乎详情页api接口
export const getZhiHuDetail = (id:string): HttpPromise<ZhiDetailRespData> => axiosClient.get({url:'/zhihudetail/'+id});

// 获取热门影视接口,仅作为post的使用示例,未使用
export const getHotMovie = (req:HotMovieReq): HttpPromise<MovieRespData> => axiosClient.post({url:'/hotmovie',data:req});


// 使用举例:
/*
 getHotMovie({start:1,count:2,city:'郑州'}).then((res) => {
      Log.debug(res.data.message)
      Log.debug("request","res.data.code:%{public}d",res.data.code)
    }).catch((err: BusinessError) => {
      Log.debug("request","err.data.code:%d",err.code)
      Log.debug("request",err.message)
    });
 */

一行代码写好一个接口,清晰直观。但是接口相关的包体还是需要定义好的:

//===知乎日报接口包体定义
export interface ZhiNewsItem {
  id:string;
  image:string;
  title:string;
  url:string;
  hint:string;
  date: string;
  isShowDivider?: boolean;

}
export interface ZhiNewsRespData {
  code: number;
  message: string;
  stories: Array<ZhiNewsItem>;
  top_stories: Array<ZhiNewsItem>;
  date: string;
}

export type ZhiDetailItem={
  types:string;
  value:string;
}
export interface ZhiDetailRespData {
  code: number;
  message: string;
  content: Array<ZhiDetailItem>;
  title: string;
  author: string;
  bio: string;
  avatar: string;
  image: string;
  more: string;

}

 

总结

通过对Axios的封装,我们可以在HarmonyOS NEXT应用开发中实现更高效、更整洁的网络请求处理。封装不仅提升了代码的可维护性和可读性,还提供了简洁、一致的使用体验。无论你是初学者还是经验丰富的开发者,都可以通过这种封装方式大幅度提高开发效率。在未来的开发中,我们推荐您使用这种封装技术来处理网络请求,以便更好地适应快速发展的移动应用开发需求。

 

 

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐