UniApp——对uni.request()进行封装,实现拦截器和typescript支持

799 阅读3分钟

引言

最近在学习uni,尝试着把之前写的一个小网站(网站地址博客地址)转换成安卓app。由于我只会web和vue,对uni几乎是零基础,所以在转换的过程中遇见了许多问题。

比如,在原网站里面我的ajax请求是基于axios的,也就是基于XMLHttpRequest的,当我把原网站中ajax模块的代码复制到uni-app中时,发现项目是跑不通的。报了一堆错,我也看不懂,但我猜,uni-app可能不支持XMLHttpRequest,导致axios不能用。

于是,我想着用uni-app提供的uni.request()来做ajax请求,但此方法仅仅具有简单的发送请求和接受响应功能,并不具备axios的拦截器、ts支持等功能。所以,我决定按照axios的使用习惯,对uni.request()进行封装,以实现拦截器和typescript支持。

实现代码

实现代码如下: UniRequest类就是我们封装的工具类,实现逻辑很简单,直接看代码和注释就可以看懂,所以不再过多描述。


interface UniRespone<T> extends UniApp.RequestSuccessCallbackResult{
	data:T
}
interface UniRequestConfig{
	baseUrl?:string
	header?:Record<string, string>
	timeout?:number // 超时限制,毫秒
}
type UniRequestInterceptor = (options: UniApp.RequestOptions) => UniApp.RequestOptions
type UniResponseInterceptor = (response: UniApp.RequestSuccessCallbackResult) => UniApp.RequestSuccessCallbackResult
type UniResponseErrorInterceptor = (err: UniApp.GeneralCallbackResult) => UniApp.GeneralCallbackResult

class UniRequest{
	constructor(deaultConfig?:UniRequestConfig){
		if(deaultConfig){
			this.defaultConfig = Object.assign(this.defaultConfig, deaultConfig)
		}
	}
	// 默认的请求配置
	private defaultConfig:UniRequestConfig = {
		baseUrl: '',
		header: {},
		timeout: 30000
	}
	// 请求拦截器
	private requestInterceptors = [] as UniRequestInterceptor[]
	// 响应拦截器
	private responseInterceptors = [] as UniResponseInterceptor[]
	// 响应错误拦截器
	private responseErrorInterceptors = [] as UniResponseErrorInterceptor[]
	public interceptors = {
		request: {
			use: (ri:UniRequestInterceptor) => {
				this.requestInterceptors.push(ri)
			}
		},
		response: {
			use: (ri:UniResponseInterceptor, ei:UniResponseErrorInterceptor) => {
				this.responseInterceptors.push(ri)
				this.responseErrorInterceptors.push(ei)
			}
		}
	}
	// 发起请求,默认配置是defaultConfig,也可以传入config参数覆盖掉默认配置中某些属性
	public request<T = any, D = any>(method: 'GET'|'POST', url:string, data?:D, config?:UniRequestConfig):Promise<UniRespone<T>>{
		return new Promise((resolve, reject) => {
			url = (config?.baseUrl || this.defaultConfig.baseUrl) + url
			let header = config?.header ? Object.assign(this.defaultConfig.header, config.header) : this.defaultConfig.header
			let timeout = config?.timeout ? config.timeout : this.defaultConfig.timeout
			let options:UniApp.RequestOptions = {
				method,
				url,
				header,
				timeout,
				data,
				success: (res) => {
					// 执行响应拦截
					for(let ri of this.responseInterceptors){
						res = ri(res)
					}
					resolve(res as UniRespone<T>)
				},
				fail: (err) => {
					// 执行响应错误拦截
					for(let ei of this.responseErrorInterceptors){
						err = ei(err)
					}
					reject(err)
				}
			}
			
			// 执行请求拦截器
			for(let ri of this.requestInterceptors){
				options = ri(options)
			}
			// 发送请求
			uni.request(options)
		})
	}
	// 发起get请求
	public get<T = any, D = any>(url:string, params?:D, config?:UniRequestConfig){
		return this.request<T, D>('GET', url, params, config)
	}
	// 发起post请求
	public post<T = any, D = any>(url:string, data?:D, config?:UniRequestConfig){
		return this.request<T, D>('POST', url, data, config)
	}
}

使用方法

UniRequest工具类的使用方法跟axios很类似,我这里用举例子的方式来说明。

假如,目前有一个get请求的接口localhost:8000/user,接收一个整数类型的id参数,返回一个User对象,那么代码如下:

// 定义User类型
interface User{
	id:number
	name:string
	age:number
}
// 生成UniRequest实例,类似于axios实例
const uniHttp = new UniRequest({
	baseUrl:'http://localhost:8000',
	timeout: 10000
})
// 添加请求拦截器,这个写法是不是和axios很像?
uniHttp.interceptors.request.use(options => {
	console.log('发起请求:', options.method, options.url)
	return options
})
// 添加响应拦截器,这个写法是不是也和axios很像?
uniHttp.interceptors.response.use(
	response => {
		console.log('接收到响应:', response.statusCode, response.data)
		return response
	},
	err => {
		console.log('请求出错啦:', err.errMsg)
		return err
	}
)
// 发起
uniHttp.get<User, {id:number}>('/user', {id: 1}).then(
	res => {
		// 这里能自动识别res.data的类型,为User类型
		console.log('学生姓名是:', res.data.name)
	},
	err => {
		console.dir('出错啦', err)
	}
)

由于例子中的接口并不存在,所以请求当然不能得到正确响应,控制台的输出是这样的:

发起请求: GET http://localhost:8000/user

请求出错啦: request:fail

出错啦 {errMsg: 'request:fail'}

可以看到拦截器是正确运行的。