100行代码打造小而美的uni-app请求库
一. 前言
在开发 uni-app 项目时,我们经常需要对接后端接口进行数据请求。虽然 uni-app 框架本身提供了uni.request
用于发起请求,但在实际项目中,我们往往会封装一些请求库来简化请求的操作,提高代码复用性和可维护性。
本文将介绍基于 uni.request
实现一款小而美的请求工具,通过大约 100 行代码的实现,为 uni-app 项目打造一个简洁高效的请求库。
二. 请求库的设计思路
1. 了解 uni.request
在 uni-app 中使用 uni.request
来发起请求,但这种直接调用 uni.request
的方式在实际开发中存在一些不足之处,比如请求逻辑过于分散、请求参数拼接繁琐等。因此,我们希望通过封装一个简单的请求库来优化这一过程。
在 uni-app 使用 uni.request
的方式:
uni.request({
// url 仅为示例,并非真实接口地址。
url: "https://www.example.com/request",
data: {
text: "uni.request",
},
header: {
// 自定义请求头信息
"custom-header": "hello",
},
success: (res) => {
console.log(res.data);
this.text = "request success";
},
});
以上的这种请求方式是在 uni-app 项目中最常见的代码书写方式,很简单也很好理解,但是无法支持 promise API 式的请求,不支持请求和响应拦截器,同时也不支持全局的变量配置,逻辑分散,不便于维护!
不过我最近看官方文档,官方已经对部分 API 进行了 Promise 封装
详情参考:官方 Promise - 封装
2. 请求库的实现目标
我的目标核心是实现一个基于 Promise 的,轻量且强大的 http 网络库,基于以上这个目标,我的请求库应该具备以下基本功能:
- 提供统一的
Promise API
- 基于
uni.request
,支持多种运行环境,浏览器 H5、小程序、APP 等 - 支持发起
GET
、POST
、PUT
、DELETE
请求 - 支持请求/响应拦截器
- 支持设置请求的
header
- 支持处理请求的
loading
状态 - 支持对请求结果进行统一处理
- 支持链式调用
三. 请求库实现
基于以上的目标特性,我会一步一步实现请求库,而它的实现应该主要包括以下几个核心点:
Config 配置项:设计一个统一的配置项对象
config
,其中包含了 baseURL、header、data、method、dataType、responseType 等网络请求的基本配置信息。这样可以确保在发起网络请求时,可以统一管理这些配置项,方便进行全局设置和覆盖。拦截器 Interceptors:设计请求拦截器和响应拦截器,提供
use
方法来添加拦截器处理函数。请求拦截器可以在发送请求前对请求参数进行处理,而响应拦截器则可以在收到响应后对响应结果进行处理。通过拦截器可以实现一些通用的网络请求处理逻辑,比如添加请求头、处理请求参数、统一处理响应结果等。链式调用:在
setBaseURL
、setHeader
、setData
等方法中都使用了链式调用的方式,即每一个方法返回当前实例的引用,使得可以连续调用多个方法来设置请求的各个参数,提高代码的可读性和可维护性。Promise 处理:在
request
方法中使用了 Promise 对网络请求的结果进行处理,包括请求成功和失败的处理逻辑。同时也通过 Promise 来处理拦截器的返回结果,保证请求和拦截器的执行顺序和逻辑。错误处理:在请求完成后,根据返回的状态码来判断请求的成功与失败,并通过不同的处理逻辑来处理不同状态下的响应结果。同时,在拦截器和请求的过程中,也会对错误进行处理,保证请求过程的稳定性和可靠性。
接下来我们分别对以上的核心点进行一一实现:
1. 构造函数
在构造函数中,初始化
config
对象和interceptors
对象,分别用来存储请求的配置信息和拦截器。config
包含了 baseURL、header、data、method、dataType、responseType、success、fail 和 complete 等属性。interceptors
包含了 request 和 response 拦截器,用来处理请求和响应的拦截操作。
通过构造函数,来定义统一的公共变量
class Http {
constructor() {
this.config = {
baseURL: "",
header: { "Content-Type": "application/json;charset=UTF-8" },
data: {},
method: "GET",
dataType: "json",
responseType: "text",
success() {},
fail() {},
complete() {},
};
}
}
2. 设置 BaseURL
定义 setBaseURL
方法,用来设置请求的基础 URL 统一请求前缀,将传入的 baseURL 参数赋值给 config.baseURL 属性。
function setBaseURL(baseURL) {
this.config.baseURL = baseURL;
return this;
}
3. 设置请求 header
定义 setHeader
方法,用来设置请求的头部信息,将传入的 header 参数与原有的 header 属性合并更新。
function setHeader(header) {
this.config.header = { ...this.config.header, ...header };
return this;
}
4. 设置请求体
定义 setData
方法,用来设置请求的数据,根据传入的数据类型判断是直接赋值还是合并更新到 config.data 属性中。
function setData(data) {
if (Array.isArray(data)) {
this.config.data = data;
} else {
this.config.data = { ...this.config.data, ...data };
}
return this;
}
5. 设置拦截器
设计请求拦截器和响应拦截器,提供 use
方法来添加拦截器处理函数。请求拦截器可以在发送请求前对请求参数进行处理,而响应拦截器则可以在收到响应后对响应结果进行处理。
通过拦截器可以实现一些通用的网络请求处理逻辑,比如添加请求头、处理请求参数、统一处理响应结果等。
this.interceptors = {
response: {
use(handler, onerror, complete) {
this.handler = handler;
this.onerror = onerror;
this.complete = complete;
},
},
request: {
use(handler) {
this.handler = handler;
},
},
};
6. 基于 Promise 对象来实现
通过以上的配置,其实我们已经完成了一半,接下来,使用这些配置好的变量按需使用 uni.request
就可以了,是不是很简单?
而 request 方法应该包含以下内容:
request
方法用来发起请求,根据传入的 URL、数据和选项进行请求配置。先处理请求的基础配置,包括 URL、baseURL、header、method、dataType 等。
接着处理拦截器,分为 request 和 response 拦截器,根据拦截器的设置进行相应的拦截操作。
最后返回一个 Promise 对象,实现异步请求的链式调用,并根据请求结果执行相应的回调处理。
接下来,我们按部就班的来实现我们最重要的 request 方法
function request(url, data, options) {
if (!options) options = {};
// 请求URL
options.url = url;
// 请求baseURL:优先级为:实时传递的 > 公共配置的
options.baseURL =
options.baseURL !== undefined ? options.baseURL : this.config.baseURL;
// 请求头:合并公共配置与实时设置的header, 且优先级实时设置会覆盖公共配置的
options.header = { ...this.config.header, ...options.header };
// 请求方式:优先级为:实时传递的 > 公共配置的
options.method = options.method || this.config.method;
// 数据格式:默认json
options.dataType = options.dataType || this.config.dataType;
// 请求体:优先级为:实时传递的 > 公共配置的
if (isArray(data)) {
options.data = data;
} else {
options.data = { ...this.config.data, ...data };
}
// 拦截器处理
let interceptors = this.interceptors;
let requestInterceptor = interceptors.request;
let responseInterceptor = interceptors.response;
let requestInterceptorHandler = requestInterceptor.handler;
// 实现 Promise
return new Promise((resolve, reject) => {
function isPromise(p) {
return p && p.then && p.catch;
}
/**
* 公用方法
* If the request/response interceptor has been locked
*/
function enqueueIfLocked(promise, callback) {
if (promise) {
promise.then(() => {
callback();
});
} else {
callback();
}
}
// 响应回调
function onresult(handler, response, type) {
enqueueIfLocked(responseInterceptor.p, function () {
if (handler) {
// 统一添加请求信息
response.request = options;
let ret = handler.call(responseInterceptor, response, Promise);
response = ret === undefined ? response : ret;
}
if (!isPromise(response)) {
response = Promise[type === 0 ? "resolve" : "reject"](response);
}
response
.then((d) => {
resolve(d.data);
})
.catch((e) => {
reject(e);
});
});
}
// 请求完成回调,不管请求成功或者失败,都会走这个方法
options.complete = (response) => {
let statusCode = response.statusCode;
let type = 0;
if ((statusCode >= 200 && statusCode < 300) || statusCode === 304) {
// 请求成功
type = 0;
onresult(responseInterceptor.handler, response, type);
} else {
// 请求错误
type = -1;
onresult(responseInterceptor.onerror, response, type);
}
// 请求完成,无论请求成功、失败都会走的回调
onresult(responseInterceptor.complete, response, type);
};
// 开始请求
enqueueIfLocked(requestInterceptor.p, () => {
options = Object.assign({}, this.config, options);
options.requestId = new Date().getTime();
let ret = options;
if (requestInterceptorHandler) {
ret =
requestInterceptorHandler.call(
requestInterceptor,
options,
Promise
) || options;
}
if (!isPromise(ret)) {
ret = Promise.resolve(ret);
}
ret.then(
(d) => {
if (d === options) {
// url处理
d.url =
d.url && d.url.indexOf("http") !== 0 ? d.baseURL + d.url : d.url;
// 是否有追加restful url
d.url = d.restURL ? d.url + d.restURL : d.url;
// 使用 uni.request 正式请求, d 为所有的请求参数
uni.request(d);
} else {
resolve(d);
}
},
(err) => {
reject(err);
}
);
});
});
}
综上所述,以上请求库的设计思路是以封装、拓展性和易用性为核心,通过配置项、拦截器、链式调用和 Promise 处理等设计,实现了一个简单但功能完善的网络请求库,适用于处理各种类型的网络请求需求。
总的来说,以上代码实现了一个简洁易用的 HTTP 请求类,结合了配置管理、拦截器功能和异步请求处理,并提供了一些常用的方法来方便进行 HTTP 请求的管理和处理。
四. 使用方式
1. npm 安装
我已经将uni-http
的请求库发到了npm
上了,并且在多个项目中得到了良好的应用,可直接使用 npm
安装使用,通过如下方式进行安装:
// 安装
npm install @anyup/uni-http -S
// 更新
npm update @anyup/uni-http
点击查看在 npm 上完整的 uni-http 请求库,欢迎大家使用:npm 地址
2. 快速上手
实例化 Http 类
Http 为一个类,所以在使用前需要实例化 new
,通过构造函数实例化一些必备参数
import { Http } from "@anyup/uni-http";
const http = new Http();
// 设置统一的头部
const header = { "Content-Type": "application/json" };
// 仅为示例 API 域名,使用时替换自己的接口域名即可
const baseURL = "https://demo.api.com";
// 设置 baseURL 和 header,支持链式调用
http.setBaseURL(baseURL).setHeader(header);
设置拦截器
- 通过
interceptors.request.use
设置请求拦截器,主要对请求 header 的配置,比如 token 等。 - 通过
interceptors.response.use
设置响应拦截器,如果需要,可以对所有的请求成功的响应数据做统一的业务处理,以简化代码。
// 设置请求拦截器
http.interceptors.request.use(
(request) => {
if (request.loading) {
// 如果配置了loading,请求开始需要显示
}
// 设置请求header
request.header["Authorization"] = "";
return request;
},
(error) => Promise.resolve(error)
);
// 设置响应拦截器
http.interceptors.response.use(
(response) => {
// 请求成功
if (!response.data) {
return Promise.reject(new Error("接口请求未知错误"));
}
// 其他业务处理
return Promise.resolve(response);
},
(error) => {
// 请求失败,业务处理
return Promise.reject(error);
},
(complete) => {
// 请求完成
if (complete.request.loading) {
// 如果配置了loading,请求完成需要关闭
}
// 其他业务处理
console.log("complete", complete);
}
);
请求示例
以登录请求为示例,通过传递 url,data,option,分别配置请求的 api 地址,请求参数,以及请求的个性化配置(是否需要请求 loading 等)。
// 登录请求示例,并配置请求时显示loading
function requestLogin(username, password) {
http
.request(
"/login",
{ username, password },
{ method: "POST", loading: true }
)
.then((res) => {
// 处理 response 响应
if (res.status === 1) {
console.log(res);
}
});
}
// 直接使用 get|post|put|delete 方式请求
function requestLogin1(username, password) {
// 也可以直接使用 post 方法
http.post("/login", { username, password }, { loading: true }).then((res) => {
// 处理 response 响应
if (res.status === 1) {
console.log(res);
}
});
}
// es6 await 风格 登录请求示例
async function requestLogin2(username, password) {
const res = await http.request(
"/login",
{ username, password },
{ method: "POST", loading: true }
);
// 处理 response 响应
if (res.status === 1) {
console.log(res);
}
}
五. 总结
通过以上代码的实现,我们成功构建了一个简洁高效的 uni-app 请求库,实现了一个小而美的 uni-app 请求库,能够满足常见的请求需求,同时除去注释,代码量也被控制在了 100 行左右。而且还实现了统一请求响应拦截器、全局错误处理等功能,使用它可以让我们的项目开发变得更加顺畅和便利。
希望本文能够对大家在 uni-app 项目中封装请求库有所帮助,让我们的开发工作更加高效和便捷。
在实际项目中,如果你有任何疑问或建议,欢迎联系我,我可以根据需求继续扩展这个请求库。
六. 结语
如果你有任何问题,或者想要共同学习交流,欢迎通过沸点联系我:点击查看沸点
剧透:篇幅有限,下篇文章我会继续以此为基础,说明如何实现批量生成 API 请求,简化代码量 99.99%,通过类的工厂模式,搞定繁琐的请求 function 定义,敬请关注!
源码
开源不易,欢迎 Start、Fork