封装 uniapp 请求库的最佳实践

news/2024/9/20 10:35:04/文章来源:https://www.cnblogs.com/wang013/p/18299357

背景

在前端开发中,HTTP 请求是与服务器进行数据交互的核心手段。无论是获取数据还是提交数据,前端应用几乎都离不开 HTTP 请求。在 uniapp 中,uni.request 是官方提供的用于发起 HTTP 请求的基础 API。然而,直接使用 uni.request 存在一些问题和不足,比如:

  1. 代码冗余:每次发起请求时都需要编写类似的配置代码,导致代码重复。
  2. 缺乏统一管理:没有统一的地方管理请求参数、头信息、错误处理等,使得代码不易维护

意义

  • 简化请求配置:在每次发起请求时,通常需要配置很多参数,比如 URL、请求头、请求体等。通过封装请求库,可以设置默认的请求参数,简化每次请求的配置操作,减少开发人员的工作量,提高开发效率。
  • 管理请求凭证:通过封装请求库,可以集中管理凭证,确保每次请求都自动携带正确的凭证。
  • 便于维护和扩展:封装请求库后,如果需要对请求逻辑进行修改或扩展,只需要在封装库中进行调整,而不需要在项目的各个地方逐一修改。此外,如果需要将请求库更换为其他库(例如 Axios),只需修改封装的请求库部分,而无需改动业务代码。
  • 提高用户体验:通过统一处理全局请求 Loading 状态,可以在请求进行中显示加载提示,提升用户体验。

实现思路

1. 把 uni.request 改为支持 Promise 调用方式

uni.request 改为支持 Promise 调用方式的好处是可以避免回调嵌套问题,并且可以借助 async/await 实现同步调用。

实现方式大概有如下两种:

1.1 通过 uni 自身提供的方法

调用 uni.request 时,如果不传入 successfailcomplete 回调函数,uni.request 的返回值将是一个 Promise 对象。

uni.request({url: "",// ... 其他配置
}).then(()=> {
}).catch(()=> {
}).finally(()=> {
});

1.2 通过 Promise 包装

new Promise((resolve, reject)=> {uni.request({url: "",success(res) {resolve(res);},fail(error) {reject(error);},complete() {}});
});

具体采用哪种方式都可以,这里选择第一种。

2. 定义默认请求参数

在请求时,通常需要设置 content-typetimeout 等信息。这些参数通常不会改变,因此可以设计为默认参数,同时保留外部覆盖默认参数值的能力。

2.1 定义默认参数

// 定义默认参数
const defaultOptions = {timeout: 15000,dataType: "json",header: {"content-type": "application/json",}
};

2.2 合并外部参数与默认参数

提供外部覆盖默认参数值的能力

const defaultConfig = {timeout: 15000,dataType: 'json',header: {'content-type': 'application/json',},
};
const wrapRequest = ({ url = '', data = {}, method = "GET", header = {} 
} = {}) => {return uni.request({...defaultConfig,url,data,method,header: {...defaultOptions.header,...header}});
}

3. 统一处理请求凭证

在大多数系统中,接口请求通常需要传递用户凭证。通常的做法是在请求的 Header 中添加 Authorization 属性。为了简化这个过程,可以通过拦截器来实现。

const TOKEN_KEY = 'token';// 处理 token
const handleToken = (config) => {const token = uni.getStorageSync(TOKEN_KEY)if (token) {config.header.Authorization = token;}
}
uni.addInterceptor("request", {invoke: function (config) {handleToken(config);}
});

另外,系统通常会有多个环境。在这种情况下,可以根据不同的环境设置不同的 BASE_URL,这也可以通过拦截器来实现。

const BASE_URL = ""; const handleURL = (config) => {const { url } = config;if (!/https|http/.test(url)) {config.url = url.startsWith("/")? `${BASE_URL}${url}`: `${BASE_URL}/${url}`;}
}uni.addInterceptor("request", {invoke: function (config) {handleURL(config);}
});

如果有其他处理需求,可以直接在这里添加。

4. 统一处理公共响应状态码

为了避免在多个地方处理公共的错误逻辑,例如凭证无效时跳转到登录页、移除本地 token 等,我们可以在全局请求响应拦截器中集中处理这些问题。

const LOGIN_INVALID_CODE_LIST = ["INVALID_TOKEN", "EXPIRED_TOKEN"];
const SUCCESS = "SUCCESS";uni.addInterceptor("request", {success(res){const { data: resData } = res;const { code, message } = resData;if (code !== SUCCESS) {// 如果响应代码在登录无效代码列表中if (LOGIN_INVALID_CODE_LIST.includes(code)) {uni.showToast({title: message,icon: "none",});uni.navigateTo({url: "/pages/login/login"});return;} else {// 处理其他错误代码return Promise.reject(resData)}}return Promise.resolve(resData)},
});

5. 封装公共方法 GET、POST、DEL、PUT

为了进一步简化请求参数,可以提供一系列方法,例如 GETPOSTDELETEPUT

export const get = (params) => wrapRequest({ ...params, method: 'GET' });
export const post = (params) => wrapRequest({ ...params, method: 'POST' });
export const put = (params) => wrapRequest({ ...params, method: 'PUT' });
export const del = (params) => wrapRequest({ ...params, method: 'DELETE' });

这样做的好处,它消除了每次调用时显式传入 HTTP 方法的需要,使代码更简洁、更易读。这样做的好处是你在调用这些方法时只需关注请求参数,而不需要重复指定 HTTP 方法。

6. 定义全局请求 Loading

在正常情况下,我们的接口通常会很快完成。然而,考虑到不同网络状况下,接口响应速度可能会变慢,从而增加用户的等待时间。为了优化用户体验,我们可以在全局请求中添加 Loading 提示,这将大大提升用户体验。

const showLoading = () => {uni.showLoading({title: '加载中',});
};const hideLoading = () => {uni.hideLoading();
};uni.addInterceptor("request", {invoke: function (request) {showLoading();return request;},complete() {hideLoading();}
});

这样每个接口请求时都会触发显示 Loading。考虑到某些接口可能不需要显示 Loading,我们可以允许用户在定义接口时明确控制是否展示 Loading

const showLoading = (loading) => {uni.showLoading({title: '加载中',});
};const hideLoading = (loading) => {uni.hideLoading();
};uni.addInterceptor("request", {invoke: function (config) {if (config.loading) {showLoading();}return request;},complete() {hideLoading();}
});const wrapRequest = ({url = '',data = {},method = 'GET',header = {},loading = true // 默认是展示 loading
} = {}) => {return uni.request({...defaultConfig,url,data,method,loading,header: {...defaultOptions.header,...header,},});
};

为了解决接口请求很快时 Loading 闪烁的问题,我们可以添加一个延迟参数。如果请求时间超过 50ms(具体阀值可以自己去定义) 才显示 Loading,否则就不展示:

const LOADING_DELAY = 50; // 50ms 延迟 
let loadingTimer;const showLoading = () => {uni.showLoading({title: '加载中',});
};const hideLoading = () => {uni.hideLoading();
};uni.addInterceptor("request", {invoke: function (config) {if (config.loading) {loadingTimer = setTimeout(showLoading, LOADING_DELAY);}return config;},complete() {clearTimeout(loadingTimer);hideLoading();}
});

7. 完整代码如下

const defaultOptions = {timeout: 15000,dataType: 'json',header: {'content-type': 'application/json',},
};
const TOKEN_KEY = 'token';
const BASE_URL = '';
const LOGIN_INVALID_CODE_LIST = ['INVALID_TOKEN', 'EXPIRED_TOKEN'];
const SUCCESS = 'SUCCESS';
const LOADING_DELAY = 50; // 50ms 延迟
let loadingTimer;const handleURL = (config) => {const { url } = config;if (!/https|http/.test(url)) {config.url = url.startsWith('/')? `${BASE_URL}${url}`: `${BASE_URL}/${url}`;}
};const handleToken = (config) => {const token = uni.getStorageSync(TOKEN_KEY);if (token) {config.header.Authorization = token;}
};const showLoading = () => {uni.showLoading({title: '加载中',});
};const hideLoading = () => {uni.hideLoading();
};uni.addInterceptor('request', {invoke: function (config) {if (config.loading) {loadingTimer = setTimeout(showLoading, LOADING_DELAY);}handleURL(config);handleToken(config);},success(res) {const { data: resData } = res;const { code, message } = resData;if (code !== SUCCESS) {// 如果响应代码在登录无效代码列表中if (LOGIN_INVALID_CODE_LIST.includes(code)) {uni.showToast({title: message,icon: 'none',});uni.navigateTo({url: '/pages/login/login',});return;} else {// 处理其他错误代码return Promise.reject(resData);}}return Promise.resolve(resData);},complete() {clearTimeout(loadingTimer);hideLoading();},
});const wrapRequest = ({url = '',data = {},method = 'GET',header = {},loading = true,
} = {}) => {return uni.request({...defaultOptions,url,data,method,loading,header: {...defaultOptions.header,...header,},});
};export const get = (params) => wrapRequest({ ...params, method: 'GET' });
export const post = (params) => wrapRequest({ ...params, method: 'POST' });
export const put = (params) => wrapRequest({ ...params, method: 'PUT' });
export const del = (params) => wrapRequest({ ...params, method: 'DELETE' });

8. 测试

import { get } from '@/utils/request';get({url: "https://api.aigcway.com/aigc/chat-category/list"
}).then((res)=> {console.log(res);
})

输出如下:

{"code": "SUCCESS","message": "操作成功","data": []
}

总结

我们完成了一个通用请求库的封装,这基本上可以满足大多数业务需求。在具体请求中,状态码处理可以根据自身业务需求进行调整。

为了掌握上面的内容,需要掌握 uni.addInterceptoruni.request 执行的完整流程。以下是整理的不同情况下的流程图,可以参考学习。

上面流程图对应示例代码:

uni.addInterceptor('request', {invoke: function (config) {console.log("interceptor invoke");},success(res) {console.log("interceptor success");},complete() {console.log("interceptor complete");},
});
uni.request({url: ""
}).then(()=> {console.log("then");
}).catch(()=> {console.log("catch");
}).finally(()=> {console.log("finally");
})

上面流程图对应示例代码:

uni.addInterceptor('request', {invoke: function (config) {console.log("interceptor invoke");},success(res) {console.log("interceptor success");},complete() {console.log("interceptor complete");},
});
uni.request({success(){console.log("success");},fail(){console.log("fail");},complete(){console.log("complete");}
});

如果大家觉得有帮助,请点赞、收藏、分享,谢谢!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/742691.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

FastStone Capture (屏幕截图) v9.2 汉化版

下载地址:https://www.mediafire.com/file/b6crzq480nyzf8v/FSCapture-9.2-CN.zip/file 软件简介: FastStone Capture 是一款出色的屏幕截图(捕捉)软件,它集图像捕捉、浏览、编辑、视频录制等功能于一身,功能完善、使用方便,值得推荐! 软件提供多种截图方式(如:活动窗…

9、IDEA集成Github

9.1、登录Github账号 9.1.1、打开IDEA的Settings界面如上图所示,打开IDEA的 Settings(设置)界面。 9.1.2、使用账号密码登录(方式一)如上图所示,在“Version Control”->“Github”中,点击“+”按钮,在登录弹窗中,输入GitHub的账号密码直接登录。注意:该方式可能由…

黑球白球巧妙异或问题

题目:一个桶里一共有a个白球和b个黑球。每次拿出2个球,并且每个球被拿出的概率相等。如果拿出一黑一白,就往桶里放进一个黑球;如果拿出两个黑或者两个白,就往桶里放进一个白球。求:最后只剩一个黑球的概率是多少?答案:如果黑球个数是偶数,最后剩下为黑球的概率是0%;如…

动态条件实现java

提交页面设计 json数据格式[{"name": "规则1","action": {"with": [{"type": "SHOW","targets": ["xd_hh_158444776217"]},{"type": "HIDE","targets": [&qu…

玩一玩yolov5 自己训练模型识别马克杯

python 虚拟环境搭建 conda create -n yolo python==3.8yolov5下载 git clone https://github.com/ultralytics/yolov5 cd yolov5 activate yolo pip install -r requirements.txt准备数据集 官方介绍:https://github.com/ultralytics/yolov5/wiki/Train-Custom-Data 建立文件…

web渗透——信息收集

切记:未经授权,禁止对任何网站进行渗透测试whois查询 常用网址: 爱站:https://www.aizhan.com/ 站长之家:https://whois.chinaz.com/ bugscaner:http://whois.bugscaner.com/ 端口扫描 常用工具: Nmap工具Masscan CMS识别 常用网址: TideFinger潮汐:http://finger.tides…

记一次处理挖矿程序xmrig的过程

现象 挖矿xmrig占用100%CPU处理过程 kill掉进程,后会立即启动# kill pid # kill 14527通过pid找到病毒文件地址 # ls -l /proc/PID/exe找到病毒文件夹目录为 /root/c3pool使用rm删除文件会报权限错误 使用lsattr查看隐藏权限为e表示可以追加 # lsattr miner.sh -------------…

Algorithm notes and references

Algorithm notes and references Version:2024/02/03 Data Structure 1. Segment Tree Beats (segb) from 题解 P4314 【CPU监控】 - He_Ren 的博客 - 洛谷博客 (luogu.com.cn) lazy tag 实际上可以看作是对于该节点表示的区间的操作序列,这也是线段树的精髓所在 push_down 操…

浅谈HTTP中Get与Post的区别

Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE。URL全称是资源描述符,我们可以这样认为:一个URL地址,它用于描述一个网络上的资源,而HTTP中的GET,POST,PUT,DELETE就对应着对这个资源的查,改,增,删4个操作。到这里,大家应该有…

ExtJS中layout的12种布局风格

extjs的容器组件都可以设置它的显示风格,它的有效值有 absolute, accordion, anchor, border, card, column, fit, form and table. 一共9种。 另外几种见: http://www.sencha.com/deploy/dev/examples/layout-browser/layout-browser.html 里面有详细的例子。 absolute …

extjs中treepanel例子

:TreePanel继承自Panel,在ExtJS中使用树控件含有丰富的属性和方法实现复杂的功能。其中Ext.tree.TreeNode代表一个树节点,比较常用的属性包括text、id、icon、checked等、异步树Ext.tree.AsyncTreeNode、树加载器Ext.tree.TreeLoader。下面介绍几个extjs中treepanel例子一、…

全球单体容量最大漂浮式风电平台“明阳天成号”正式亮相

7月12日,全球单体容量最大的16.6MW漂浮式风电平台“明阳天成号”启航仪式在中船黄埔文冲造船厂举办,中山市委书记、市人大常委会主任郭文海主礼启航仪式。“明阳天成号”于7月3日完成吊装,经过各项调试准备工作后正式亮相,并将择日拖航至广东阳江海域。据测算,“明阳天成号…