大家好,今天我想分享一些关于uni-app
开发中封装高效、优雅的 request 请求的经验。之前我在uni-app开发小程序:项目架构以及经验分享的文章中,已经分享了一些有用的经验技巧,包括二次封装uni-app
的 API。下面我将详细介绍如何封装一个强大且易用的request
请求,这里封装也仅仅是提供一个思路,大家可以在写原生小程序时候也可以进行借鉴。先赞后看,月入百万!!!
为什么需要封装 request 请求?
在uni-app
开发中,我们经常需要与后端服务器进行数据交互。为了提高开发效率、代码的可维护性以及降低重复代码的使用,我们通常会对网络请求进行封装。
封装 request 请求的好处有很多:
简化代码:将一些重复性的请求处理逻辑抽离出来,使得业务代码更加清晰简洁,易于阅读和维护。
易于管理:统一管理接口地址、错误处理和请求拦截,方便后期维护和更新。增强可扩展性:如果后端接口发生变化,只需要修改请求封装的部分而不影响业务代码。
支持 async
、await
,以提高代码的可读性和简洁性。
在请求库中实现全局的 loading 功能,让用户在发送请求时能够看到加载动画,增强用户体验。
统一处理请求错误,例如网络连接失败时,给予用户友好的错误提示,提高用户满意度。
考虑实现多种请求方式,包括 GET、POST、PUT 等,以满足不同场景下的需求。
为了避免重复请求,可以实现请求拦截功能,在请求发送前判断是否已经在进行相同的请求,如果是,则取消重复请求。
先上一下最终的使用方式:
api.js
// 引入请求
import request from '@/utils/request'
//接口示例
export const info = data => request.post('/v1/api/info', data)
复制代码
页面中使用
<script>
import { info } from '@/api/user.js'
export default {
methods: {
async getUserinfo() {
let res = await info()
}
}
}
</script>
复制代码
coding
1.创建基础请求
首先,我们导入了一些公共方法,比如toast
用于显示提示信息,clearStorageSync
和getStorageSync
用于操作本地缓存,还有useRouter
用于跳转页面等。
然后,我们定义了一个名为baseRequest
的异步函数。这个函数接收四个参数:url
表示请求的地址,method
表示请求方法,默认GET
请求,data
表示要发送的数据,默认为空对象,loading
表示是否显示加载动画,默认为true
。
在函数内部,我们构建了一个Promise
对象,用于支持async
、await
调用。
在异步请求中,我们使用了uni.request
方法发送请求。我们在请求中传入了请求地址、请求方法、请求头、数据、和超时时间等信息。
如果请求成功,并且状态码为200
,那么我们会处理返回的数据。这里的处理逻辑可以根据实际业务需求来修改。如果返回的resultCode
为PA-G998
(业务逻辑),表示用户未登录或登录过期,我们会清除本地缓存并跳转到登录页面。否则,我们会将请求成功返回的数据传递给 Promise 对象的 reslove 方法。
如果请求失败,我们会显示网络连接失败的提示,并将错误信息传递给Promise
对象的reject
方法。
import {toast, clearStorageSync, getStorageSync, useRouter} from './utils' // 公共方法
import {BASE_URL} from '@/config/index' //获取请求域名
const baseRequest = async (url, method, data = {}, loading = true) =>{
let header = {}
return new Promise((reslove, reject) => {
uni.request({
url: BASE_URL + url,
method: method || 'GET',
header: header,
timeout: 10000,
data: data || {},
success: (successData) => {
const res = successData.data
if(successData.statusCode == 200){
// 业务逻辑,自行修改
if(res.resultCode == 'PA-G998'){
clearStorageSync()
useRouter('/pages/login/index', 'reLaunch')
}else{
reslove(res.data)
}
}else{
toast('网络连接失败,请稍后重试')
reject(res)
}
},
fail: (msg) => {
toast('网络连接失败,请稍后重试')
reject(msg)
}
})
})
}
复制代码
2.简化入参
上面只是封装了一个最最基础的请求,该方法接受的参数比较多,这个时候我们就需要去做一次简化参数的操作:
首先我们创建一个名为request
的对象,并使用forEach
方法遍历包含不同HTTP
请求方法的数组。对于每个 HTTP 请求方法,它会定义一个对应的函数,并将其作为request
对象的属性。
这样,在使用request
对象时,可以直接调用request.GET()
、request.POST()
等方法来发起不同类型的 HTTP 请求,而不需要每次都显式地指定请求的方法。这样可以使代码更加简洁和易于维护。
最后导出request
对象
const request = {}
['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
request[method] = (api, data, loading) => baseRequest(api, method, data, loading)
})
export default request
复制代码
使用方式如下:
/api/user.js
文件
import request from '@/utils/request'
//个人信息
export const info = data => request.post('/v1/api/info', data)
复制代码
页面使用:
import { info } from '@/api/user.js'
export default {
onLoad() {
this.getUserinfo()
},
methods: {
async getUserinfo() {
let res = await info()
}
}
}
复制代码
禁止重复请求
为了节省网络资源、提高性能和响应速度以及避免数据错误,我们需要对发起的请求做一些限制来避免重复请求,常见的限制方法如下:
1.使用防抖和节流:可以使用防抖和节流的技术来控制请求的触发频率,确保在一段时间内只发起一次请求。2.设置请求锁:可以在发起请求之前设置一个请求锁,防止重复触发请求。3.合理设计页面和交互逻辑:在页面设计和交互逻辑中,合理安排请求的时机,避免不必要的重复请求。
我们可以在封装的接口请求中添加一个请求队列,如果有当前发起且没有返回结果的,就不允许再次请求,具体实现思路如下:
1.创建一个存放唯一 ID 的Map
对象 2.当请求接口时候通过拿到的method
、url
、params
、来生成唯一ID
3.请求完成后,把当前ID
从对象中删除。
我们把检测唯一ID
这个功能提炼出来,单独去封装一个class
去实现;具体实现代码如下:
新建/utils/requestManager.js
文件,创建一个对象,并初始化一个名为idMap
的对象,最后导出对象
class RequestManager {
constructor() {
this.idMap = new Map()
}
}
export default RequestManager
复制代码
根据method
、url
、params
、来生成唯一ID
,这里要注意的是我们的params
需要进行序列化处理,不然如果同一个接口、相同的请求方式、参数顺序不同也会判断为不同的请求。
class RequestManager {
/**
* 生成唯一ID的方法
* @param {string} method - 请求方法
* @param {string} url - 请求URL
* @param {object} params - 请求参数
* @returns {string} - 生成的唯一ID
*/
generateUniqueId(method, url, params) {
const idString = `${method}-${url}-${this.serializeObject(params)}`
let id = 0;
for (let i = 0; i < idString.length; i++) {
id = ((id << 5) - id) + idString.charCodeAt(i)
id |= 0;
}
return id.toString()
}
/**
* 序列化对象为字符串
* @param {object} obj - 要序列化的对象
* @returns {string} - 序列化后的字符串
*/
serializeObject(obj) {
const keys = Object.keys(obj).sort()
const serializedObj = {}
for (let key of keys) {
const value = obj[key]
if (value !== null && typeof value === 'object') {
serializedObj[key] = this.serializeObject(value)
} else {
serializedObj[key] = value
}
}
return JSON.stringify(serializedObj)
}
}
复制代码
写完生成方法,相对应的实现一下删除方法:
class RequestManager {
/**
* 根据ID删除map对象中的请求信息
* @param {string} id - 要删除的唯一ID
*/
deleteById(id) {
this.idMap.delete(id)
}
}
复制代码
上面就实现了基本的功能,下面我们写一个方法,去组合一下上面的功能,简化使用:
class RequestManager {
/**
* 生成唯一ID,并将ID和请求信息存储到map对象中
* @param {string} method - 请求方法
* @param {string} url - 请求URL
* @param {object} params - 请求参数
* @returns {string|boolean} - 生成的唯一ID,如果存在相同id则返回false
*/
generateId(method, url, params) {
const id = this.generateUniqueId(method, url, params)
if (this.idMap.has(id)) {
return false
}
this.idMap.set(id, { method, url, params })
return id
}
}
复制代码
到这里我们的方法就写完了,下面来看一下如何使用:
//引入方法
import RequestManager from '@/utils/requestManager.js'
const manager = new RequestManager() //创建
const baseRequest = async (url, method, data = {}, loading = true) =>{
// 生成唯一ID, 如果返回false 代表重复请求
let requestId = manager.generateId(method, url, data)
if(!requestId) {
console.log('重复请求')
return false
}
return new Promise((reslove, reject) => {
uni.request({
complete: ()=>{
// 请求完成,清除当前请求的唯一ID
manager.deleteById(requestId)
},
})
})
}
复制代码
添加全局 loading
添加全局loading
就比较简单了,我们前面定义了入参数loading
,如果为true
,在创建Promise
后,调用uni.showLoading
即可。同时需要在uni.request
中添加complete
方法,在请求完成后去关闭loading
。
const baseRequest = async (url, method, data = {}, loading = true) =>{
return new Promise((reslove, reject) => {
// 开启loading
loading && uni.showLoading({title: 'loading'})
uni.request({
// ...
complete: ()=>{
// 关闭loading
uni.hideLoading()
},
// ...省略下方代码
})
})
}
复制代码
结尾
所有代码已放到github
;请访问 uni-app-template,如果觉得不错,记得给个star
!
往期推荐
评论