#封装优雅、高效的uni-app请求:让开发更轻松

大家好,今天我想分享一些关于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用于显示提示信息,clearStorageSyncgetStorageSync用于操作本地缓存,还有useRouter用于跳转页面等。

然后,我们定义了一个名为baseRequest的异步函数。这个函数接收四个参数:url表示请求的地址,method表示请求方法,默认GET请求,data表示要发送的数据,默认为空对象,loading表示是否显示加载动画,默认为true

在函数内部,我们构建了一个Promise对象,用于支持asyncawait调用。

在异步请求中,我们使用了uni.request方法发送请求。我们在请求中传入了请求地址、请求方法、请求头、数据、和超时时间等信息。

如果请求成功,并且状态码为200,那么我们会处理返回的数据。这里的处理逻辑可以根据实际业务需求来修改。如果返回的resultCodePA-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.当请求接口时候通过拿到的methodurlparams、来生成唯一ID
3.请求完成后,把当前ID从对象中删除。

我们把检测唯一ID这个功能提炼出来,单独去封装一个class去实现;具体实现代码如下:

新建/utils/requestManager.js文件,创建一个对象,并初始化一个名为idMap的对象,最后导出对象

class RequestManager {
    constructor() {
        this.idMap = new Map()
    }
}

export default RequestManager

根据methodurlparams、来生成唯一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