前言
随着业务的需求,项目需要支持H5、各类小程序以及IOS和Android,这就需要涉及到跨端技术,不然每一端都开发一套,人力成本和维护成本太高了。团队的技术栈主要以Vue为主,最终的选型是以uni-app+uView2.0作为跨端技术栈。以前一直听别人吐槽uni-app怎么怎么不好,但是没什么概念,这一次需要为团队开发一个项目的基础框架和一些示例页面,主要是支持路由拦截、http请求多实例、请求数据加密以及登录功能封装,发现uni-app的生态不怎么健全,比如我们项目很需要的路由拦截,http请求拦截,这些都没有提供,对于跨端的兼容问题也挺多的。这篇文章聊聊http请求封装的调研,以及最终的选择和实现。
实现http请求的方式
- 使用原生
uni.request - 对
uni.request进行封装 - 使用
luch-request插件
使用原生uni.request
对于直接使用uni.request进行接口请求,应该是较少人的选择吧。这个跟我们现在较少的直接使用Ajax的原因是一致,多个请求嵌套,又进入回调地狱的圈套,这是我们不愿看到的以会对其做一层Promise的封装。原生uni.request缺少接口请求拦截,这也是我们必不可少的一环。
对uni.request进行封装
我们已经了解原生uni.request的硬伤,1、不支持Promise从而导致容易引发回调地狱。2、不支持请求拦截和响应拦截,从而导致使用的诸多不便。发现问题,就应当解决问题,所以我们可以对uni.request做进一步的封装,使其支持我们想要的功能。
request封装
// request.js
// lodash 合并函数,也可以自己实现
import merge from "lodash.merge";
// 默认配置
const DEFAULT_CONFIG = {
baseUrl: "http://xxx.api",
data: {},
header: {},
method: "post",
timeout: 150000,
dataType: "json",
responseType: "text",
sslVerify: true,
withCredentials: false,
firstIpv4: false,
};
class Request {
constructor(options = {}) {
// 合并用户自定义配置
this.config = merge({}, DEFAULT_CONFIG, options);
}
// 请求拦截 主要是合并url,合并接口特定配置,可以根据自己情况进行扩展
requestInterceptor(url, data, config, method) {
const { baseUrl } = this.config;
// 拼接Url
url = baseUrl + url;
const configs = {
...this.config,
url,
data,
...config,
method,
};
// 返回组装的配置
return configs;
}
// 响应拦截,这里只是做了示例,可以根据自己情况进行扩展
async responseInterceptor(res) {
const { data: _data } = res;
const { code, message, data } = _data;
if (code !== "200") {
this.handleError(message);
return Promise.reject(message);
}
return data;
}
// 请求方法,做了Promise封装,返回Promise
/**
* @param {String} url 接口
* @param {Object} data 参数
* @param {Object} config 某个接口自定义配置
* @param {String} method 请求方法,只实现post和get,这么做了原因是 只有这两个没有兼容问题
* @returns
*/
request(url, data, config, method) {
// 显示loading
uni.showLoading();
// 请求拦截,返回处理过的结果配置
const _config = this.requestInterceptor(url, data, config, method);
// Promise 封装
return new Promise((resolve, reject) => {
uni.request({
..._config,
success: (res) => {
// 这种简写方式一时看不懂,可以替换为注释的写法
this.responseInterceptor(res).then(resolve).catch(reject);
// this.responseInterceptor(res).then(res => {
// resolve(res)
// }).catch(err => {
// reject(err)
// });
},
fail: (err) => {
// 提示错误
this.handleError(err.message);
console.log("fail", err);
},
complete: () => {
// 关闭Loading
uni.hideLoading();
},
});
});
}
// 只实现post和get,这么做了原因是 只有这两个没有兼容问题
// 需要其他方式,可以以同样的方式自行扩展
/**
* get请求
* @param {String} url 接口
* @param {Object} data 请求参数 可选
* @param {Object} config 接口自定义配置 可选
* @returns
*/
get(url, data = {}, config = {}) {
return this.request(url, data, config, "GET");
}
/**
* post请求
* @param {String} url 接口
* @param {Object} data 请求参数 可选
* @param {Object} config 接口自定义配置 可选
* @returns
*/
post(url, data = {}, config = {}) {
return this.request(url, data, config, "POST");
}
// 错误提示
handleError(title) {
uni.showToast({
title,
icon: "none",
});
}
}
export default Request;
引用
// main.js
import Request from "./utils/request";
// 初始化实例时,可以传入配置,也可以不传,使用默认配置
// const config = { baseUrl: 'xxx.api.com'}
// const http = new Request(config);
const http = new Request();
// 挂在到uni全局
uni.http = http;
// 挂在到Vue实例
Vue.prototype.http = http;
使用方式
// .vue 文件内调用
this.http.post("table/list", { name: "小明"}, {})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
// 全局调用
uni.http.post(url, data, config)
.then(res=> {
console.log(res)
})
.catch(err=> {
console.log(err)
})
使用luch-request插件
对于大多数人来讲,有现成的轮子不用,自己封装那是不可能的。虽然以上对于uni.request的封装只有一百多行代码,但也包含了挺多的知识点,以及对于工具库封装的思考,同时功能也不是特别完善。经过一番调研,luch-request算是做的比较好的请求库,有三万多的下载量,使用的总体感觉,文档挺完善的,使用方式跟axios也比较像,最主要的是功能完善,没发现什么bug,兼容性也挺好的,最终也是选择基于它做进一步的封装。
目录结构
request封装
// src/utils/request.js
// 引入 luch-request 插件
import luchRequest from "./luch-request/index";
// 默认配置,和luch-request基本一致
const defeaultConfig = {
baseURL: "",
header: {},
method: "POST",
dataType: "json",
// #ifndef MP-ALIPAY
responseType: "text",
// #endif
// 注:如果局部custom与全局custom有同名属性,则后面的属性会覆盖前面的属性,相当于Object.assign(全局,局部)
custom: {}, // 全局自定义参数默认值
// #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
timeout: 60000,
// #endif
// #ifdef APP-PLUS
sslVerify: true,
// #endif
// #ifdef H5
// 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+)
withCredentials: false,
// #endif
// #ifdef APP-PLUS
firstIpv4: false, // DNS解析时优先使用ipv4 仅 App-Android 支持 (HBuilderX 2.8.0+)
// #endif
// 局部优先级高于全局,返回当前请求的task,options。请勿在此处修改options。非必填
// getTask: (task, options) => {
// 相当于设置了请求超时时间500ms
// setTimeout(() => {
// task.abort()
// }, 500)
// },
// 全局自定义验证器。参数为statusCode 且必存在,不用判断空情况。
validateStatus: (statusCode) => {
// statusCode 必存在。此处示例为全局默认配置
return statusCode >= 200 && statusCode < 300;
},
};
// 错误提示
const handleError = (title) => {
uni.showToast({
title,
icon: "none",
});
};
/**
* 创建http实例
* @param {Object} config 用户自定义配置
*/
function request(config) {
// 对luch-request 实例化,并且合并用户配置和默认配置
const request = new luchRequest({
...defeaultConfig,
...config,
});
// 请求拦截,可以根据需求自定义配置
request.interceptors.request.use(
(config) => {
uni.showLoading();
return config;
},
(err) => {
handleError(err.message);
return Promise.reject(err);
}
);
// 响应拦截,可以根据需求自定义配置
request.interceptors.response.use(
async (response) => {
try {
uni.hideLoading();
return await intercept(response);
} catch (err) {
handleError(err.message);
return Promise.reject(err);
}
},
(err) => {
handleError(err.message);
return Promise.reject(err);
}
);
return request;
}
/**
* 响应拦截器,抽成一个方法,比较灵活
* @param {*} promise
*/
async function intercept(response) {
const { data } = response || {};
if (data?.code !== 200) throw new Error(data.message);
return data?.data;
}
export default request;
引用
// main.js
import request from "./utils/request";
// 初始化实例时,可以传入配置,也可以不传,使用默认配置
// config 配置与luch-request配置一致,可以自定义
// const config = { baseURL: 'xxx.api.com'}
const http = request(config);
// 挂在到uni全局
uni.http = http;
// 挂在到Vue实例
Vue.prototype.http = http;
使用方式
// .vue 文件内调用
// this.http.post(url, data, config)
this.http.post("table/list", { name: "小明"}, {})
.then((res) => {
console.log(res);
})
.catch((err) => {
console.log(err);
});
// 全局调用
uni.http.post(url, data, config)
.then(res=> {
console.log(res)
})
.catch(err=> {
console.log(err)
})
小结
关于uni-app实现http请求的探索到这里就告一段落了,三种方式都是可以,不过直接使用uni.request还是有些繁琐,缺少了Promise封装和请求拦截,所以可以采用第二种方式,对其进行一定的封装,也能保证一定的可配置性和可维护性。当然,自己的封装,也只能根据需求,做相应的封装,没有第三方轮子那么完善,自己封装也需要一定的封装能力,所以有了luch-request的出场,他的封装跟axios还是比较像的,回到了我们熟悉的节奏。当然,使用第三方库有优点,也有相应的去缺点,由于uni-app的跨端性,可能导致luch-request出现不可用或者未知的错误,也是有可能,所以怎样选择,还是需要根据需求而定。
具体的代码,我写好了源码以及示例,并且发布到Github,可以查看,有问题也欢迎指出。