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

•request和response拦截器

•转换request和response的data数据

•自动转换JSON data数据 

 axios三方库安装

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

ohpm install @ohos/axios

 axios三方库封装的意义

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

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

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

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

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

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

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

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

封装后的使用效果

//axiosClient.ets
//author:csdn猫哥(blog.youkuaiyun.com/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.youkuaiyun.com/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.youkuaiyun.com/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)
    });
 */

一行代码写好一个接口,清晰直观。但是接口相关的包体还是需要定义好的。定义接口类型字段麻烦吗?参见博主的根据json自动生成ts代码神器工具介绍。

//===知乎日报接口包体定义
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应用开发中实现更高效、更整洁的网络请求处理。封装不仅提升了代码的可维护性和可读性,还提供了简洁、一致的使用体验。无论你是初学者还是经验丰富的开发者,都可以通过这种封装方式大幅度提高开发效率。在未来的开发中,我们推荐您使用这种封装技术来处理网络请求,以便更好地适应快速发展的移动应用开发需求。

作者:csdn猫哥 blog.youkuaiyun.com/qq8864,转载请注明出处。

团队:坚果派
团队介绍:坚果派由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉。团队成员聚集在北京,上海,南京,深圳,广州,宁夏等地,目前已开发鸿蒙原生应用,三方库60+,欢迎交流。

写在最后

最后,推荐下笔者的业余开源app影视项目“爱影家”,推荐分享给与我一样喜欢免费观影的朋友。
注:因涉及免费观影,该项目仅限于学习研究使用!请勿用于其他用途!

开源地址:爱影家app开源项目介绍及源码

https://gitee.com/yyz116/imovie

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

特立独行的猫a

您的鼓励是我的创作动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值