Skip to content

xRequest - Library ​

介绍 ​

这是一个核心的极简请求库,拦截、事件监听一应俱全。事件监听可重复叠加。 可跨页面中断所有请求,同时包含 dev 模式,用来统计并打印全部请求。

还能对接口进行缓存,减少重复请求。

当前实现是全局单例:任意处 new xRequest() 都会回到同一个实例。域名、Header、拦截器全局共享;每次 request 使用独立上下文,支持并发。

演示与封装见 pages/request/:

  • xrequest.uvue:页面演示
  • api.uts:业务请求封装
  • interface.uts:统一响应结构
  • store/useUserStore.uts:Pinia 登录态,token 从这里读取

演示 ​

预览

使用 ​

参考 demo 页面 /pages/request/xrequest。

导入 ​

ts
import type { xRequestOptions, xRequestOptionsCallBack, xRequestResult, xRequestHistoryType } from "@/uni_modules/tmx-ui"
import { xRequest, xRequestCall } from "@/uni_modules/tmx-ui"

设置全局域名(非必须) ​

有多个域名时,也可以在单次请求里配置 hostUrl,所以这一步不是必须的。

ts
xRequest.setHostUrl(url)

发起请求 ​

推荐直接走静态方法,或先取单例:

ts
// 等价于 xRequest.getInstance().request(opts)
const result = await xRequest.request<any>({
	url: "/web/atcilDetail/",
	data: { id: 2 }
})

const rq = xRequest.getInstance()
rq.request<any>({ url: "/web/atcilDetail/", data: { id: 2 } })

new xRequest(opts?) 也会回到同一个实例;传入 opts 时只更新单例默认参数,不影响进行中的请求。

ts
const rq = new xRequest()
rq.request({ url: "/web/atcilDetail/", data: { id: 2 } })

类型参考 ​

ts
export type xRequestMethond = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "HEAD" | "OPTIONS"

/**
 * xRequest 请求配置
 */
export type xRequestOptions = {
	/**
	 * 访问前缀域名。提供后覆盖 setHostUrl;不提供则用全局域名。
	 */
	hostUrl?: string,
	/** 定义服务器成功的状态码,不等于此值会进入失败状态。 */
	successStatusCode?: number,
	url?: string,
	/** 缓存有效期,毫秒。开启缓存后,有效期内直接返回上次结果。 */
	cacheTime?: number,
	/** 是否启用缓存,仅 GET / POST 生效 */
	useCache?: boolean,
	data?: any,
	header?: UTSJSONObject,
	method?: xRequestMethond,
	timeout?: number,
	firstIpv4?: boolean,
	/** 请求前是否显示 loading 遮罩 */
	showLoadToast?: boolean,
	/** 请求成功后是否提示 */
	showSuccessToast?: boolean,
	/** 出错时是否显示提示 */
	showErrorToast?: boolean,
	dataType?: string | null,
	responseType?: string | null,
	/** loading 提示文本 */
	loadToastText?: string,
	/**
	 * 成功提示。不提供时先尝试读取返回结果的 msg 字段。
	 * 提供后只展示你给的文本。
	 */
	successToastText?: string,
	/** 失败提示。提供后只展示你给的文本。 */
	errorToastText?: string,
}

/**
 * 监听事件中的请求参数(字段均为必填,toast 文本可选)
 */
export type xRequestOptionsCallBack = {
	hostUrl: string,
	successStatusCode: number,
	useCache: boolean,
	cacheTime: number,
	url: string,
	data: any,
	header: UTSJSONObject,
	method: xRequestMethond,
	timeout: number,
	firstIpv4: boolean,
	showLoadToast: boolean,
	showSuccessToast: boolean,
	showErrorToast: boolean,
	dataType?: string | null,
	responseType?: string | null,
	loadToastText?: string,
	successToastText?: string,
	errorToastText?: string,
}

export type xRequestResult<T = any> = {
	data: T | null,
	statusCode: number,
	header: any,
	cookies: string[],
}

export type xRequestHistoryType<T = any> = {
	id: string,
	loading: boolean,
	loaded: boolean,
	/** 状态标志 */
	status: string,
	/** 请求总时间,毫秒 */
	time: number,
	/** 请求的接口 */
	api: string,
	/** 请求成功后的数据 */
	result: T
}

默认值(单例内部):method 为 GET,timeout 为 6000,cacheTime 为 60 * 1000,useCache 为 false,successStatusCode 为 200。cacheTime 为 0 时按 60000 毫秒处理。

静态方法 ​

以下静态方法可在任意页面或 uts 中导入使用,全局生效。

函数参数说明
getInstanceopts?: xRequestOptions | null取全局唯一实例。传入 opts 时作为单例默认请求参数。
requestopts?: xRequestOptions | null走单例发请求,等价于 getInstance().request(opts),返回 Promise<xRequestResult<T>>
setAuthisPass: boolean设为 false 后,所有新发起的请求都会被阻止,包括跨页面。
setHeaderheader: UTSJSONObject | null设置后所有新请求都会带上。实例或单次请求里的 header 会与它合并。
setHostUrlurl: string | null设置后全局生效。单次请求配置了 hostUrl 会覆盖全局。
setDevdev: boolean开启后统计并收集整个应用的请求记录。
getHistory-返回应用运行期间的请求日志。
setShowToastshowLoadToast = true, showSuccessToast = true, showErrorToast = true设置全局 toast 行为,单次请求可覆盖。

实例方法 ​

new xRequest() / xRequest.getInstance() 拿到的是同一个实例。

方法类型说明
setOptionsopts?: xRequestOptions | null设置单例默认请求参数。header 会与全局 setHeader 合并,不改写进行中的请求。
addEventListenerevent: xRequestEventType, fun添加全局事件监听,对后续所有请求生效。可重复添加,相同事件会一并执行。
clearEventListenerevent?: xRequestEventType | null移除监听。不传 event 则清空全部。
abort-中断当前进行中的全部请求。返回 boolean:至少中断一条为 true。
requestopts?: xRequestOptions | null发起请求,返回 Promise<xRequestResult<T>>

监听挂在单例上,是全局的。演示页在添加监听前会先 clearEventListener(),避免重复点击叠加。

addEventListener 说明 ​

事件名 ​

请求前请求成功后改写结果中断超时错误授权被中止成功完成
beforeafteraborttimeouterrorauthsuccesscomplete

回调签名 ​

ts
export type xRequestEventType = "before" | "after" | "abort" | "timeout" | "error" | "auth" | "success" | "complete"

/** auth:返回 false 将中断后续请求 */
export type xRequestAuthFun = (arg: xRequestOptionsCallBack) => Promise<boolean>
/** before:返回 false 中断本次请求;返回配置对象则合并进本次请求参数 */
export type xRequestBeforeFun = (arg: xRequestOptionsCallBack) => Promise<boolean | xRequestOptions>
/** after:返回值会覆盖 success 的最终结果 */
export type xRequestAfterFun = (arg: xRequestResult) => Promise<xRequestResult>
/** abort / timeout / error / success / complete,返回值内部不采用 */
export type xRequestResultEventFun = (arg: xRequestResult) => Promise<unknown>
  • auth / before 入参是请求配置 xRequestOptionsCallBack。
  • 其余事件入参是请求结果 xRequestResult。
  • before 任一回调返回 false,本次请求不会真正发出。
  • after 必须返回 Promise<xRequestResult>,会覆盖最终数据。
  • complete 无论成功失败都会触发。

业务封装示例 ​

演示页用 Pinia useUserStore 存 token,pages/request/api.uts 里统一带 header、处理业务码,并对列表接口开了缓存。

ts
import { xRequest } from "@/uni_modules/tmx-ui"
import type { xRequestMethond, xRequestOptions, xRequestResult } from "@/uni_modules/tmx-ui"
import { useUserStore } from "@/store/useUserStore.uts"
import { resultDataType, resultListType, LISTPAGEART_TYPE } from "./interface"

function rq<T>(url: string, method: xRequestMethond = "POST", customData: UTSJSONObject = {} as UTSJSONObject, useCache: boolean = false): Promise<T | null> {
	const userStore = useUserStore()
	let token = userStore.token
	xRequest.setHostUrl("https://mockapi.eolink.com/LRViGGZ8e6c1e8b4a636cd82bca1eb15d2635ed8c74e774")
	return new Promise<T | null>((resovel, rej) => {
		xRequest.request<resultDataType<T>>({
			url: url,
			method: method,
			data: customData,
			useCache: useCache,
			loadToastText: "请求中",
			header: {
				"token": token,
				"Content-Type": "application/json"
			} as UTSJSONObject
		} as xRequestOptions)
			.then((res: xRequestResult<resultDataType<T>>) => {
				if (res.statusCode != 200) {
					uni.showModal({
						title: "系统错误",
						content: `错误码:${res.statusCode},发生了系统错误,请重试`
					})
					rej(null)
					return
				}
				let reqData = res.data
				if (typeof reqData != 'object' || reqData == null) {
					uni.showModal({
						title: "服务器异常",
						content: `服务器没有正确返回json数据格式,请联系管理员`
					})
					rej(null)
					return
				}
				if (reqData.code == -1) {
					uni.showToast({ title: "未登录", icon: "none" })
					rej(null)
					return
				}
				if (reqData.code != 0) {
					uni.showToast({ title: reqData.msg, icon: "none" })
					rej(null)
					return
				}
				resovel(reqData.data)
			})
			.catch(() => {
				rej(null)
			})
	})
}

export class api {
	public static async getArtListArt<T>(arg: UTSJSONObject): Promise<T[]> {
		let result = await rq<resultListType<T>>('/admin/get_actilList/', 'POST', arg, true)
		if (result == null) return [] as T[]
		return Promise.resolve(result.datalist)
	}
}

统一响应结构见 pages/request/interface.uts:

ts
export type resultDataType<T = any> = {
	msg: string,
	code: number,
	data: T | null
}

export type resultListType<T> = {
	pagetCount: number,
	count: number,
	page: number,
	listCount: number,
	datalist: T[]
}

页面示例 ​

vue
<template>
	<x-sheet>
		<x-text font-size="18" class=" text-weight-b mb-8">xRequest - Library</x-text>
		<x-text color="#999999">
			这是一个核心的极简请求库,拦截,事件监听一应俱全。而且事件监听可重复叠加。可跨页面中断所有请求,同时包含了dev模式可
			以统计所有的请求并打印。
		</x-text>
	</x-sheet>

	<x-sheet>
		<x-text font-size="18" class=" text-weight-b mb-8">设置全局请求域名</x-text>
		<x-sheet color="info" dark-color="#333" :margin="['0']">
			<x-text>当前:{{hostUrl}}</x-text>
		</x-sheet>
		<view class="flex flex-row flex-row-center-between mt-12">
			<x-button @click="setGloabalHostUrl" width="45%">设置域名</x-button>
			<x-button @click="clearGloabalHostUrl" width="45%">删除域名</x-button>
		</view>
	</x-sheet>

	<x-sheet>
		<x-text font-size="18" class=" text-weight-b mb-8">添加监听事件,可重复添加合并执行</x-text>
		<x-sheet color="info" dark-color="#333" :margin="['0']">
			<x-text>监听事件执行状态:{{status}}</x-text>
			<x-text>结果:{{resulte}}</x-text>
		</x-sheet>
		<view class="flex flex-row flex-row-center-between mt-12">
			<x-button @click="request" :block="true">请求接口</x-button>
		</view>
	</x-sheet>

	<x-sheet>
		<x-text font-size="18" class=" text-weight-b mb-8">打开dev记录所有请求</x-text>
		<x-text color="#999999">
			打开为dev后,请重新发起请求,将会全局记录所有页面的请求记录和日志
		</x-text>
		<view class="flex flex-row flex-row-center-between my-16">
			<x-button @click="openDev" :block="true">打开开发模式</x-button>
		</view>
		<x-sheet color="info" dark-color="#333" :margin="['0']">
			<x-text>{{historyRq}}</x-text>
		</x-sheet>
	</x-sheet>

	<x-sheet>
		<x-text font-size="18" class=" text-weight-b mb-8">Pinia 用户 Store</x-text>
		<x-text color="#999999" class="mb-8">
			请求封装从 store/useUserStore 读取 token。这是真 Pinia(defineStore),并已开启持久化。
		</x-text>
		<x-sheet color="info" dark-color="#333" :margin="['0']">
			<x-text>登录态:{{userStore.isLogin}}</x-text>
			<x-text>token:{{userStore.token}}</x-text>
			<x-text>用户:{{userStore.user.naicename}}</x-text>
		</x-sheet>
		<view class="flex flex-row flex-row-center-between mt-12">
			<x-button @click="mockLogin" width="45%">模拟登录</x-button>
			<x-button @click="mockLogout" width="45%">退出登录</x-button>
		</view>
	</x-sheet>

	<x-sheet>
		<x-text font-size="18" class=" text-weight-b mb-8">xRequest真实请求Api示例</x-text>
		<x-text color="#999999" class="mb-n8">
			封装见 pages/request/api.uts,token 来自 Pinia useUserStore。本接口开启缓存,1 分钟内重复请求直接返回上次结果。
		</x-text>
		<x-button :block="true" @click="testRq">请求作者服务器</x-button>
	</x-sheet>

	<x-devtool></x-devtool>
</template>

<script setup lang="ts">
	import { ref, computed } from 'vue'
	import type { xRequestResult, xRequestHistoryType } from "@/uni_modules/tmx-ui"
	import { xRequest, xRequestCall } from "@/uni_modules/tmx-ui"
	import { api } from "./api"
	import { LISTPAGEART_TYPE, UseInfo } from "./interface"
	import { useUserStore } from "@/store/useUserStore.uts"

	const status = ref<string>("")
	const resulte = ref<xRequestResult<any> | null>(null)
	const userStore = useUserStore()

	const hostUrl = computed<string>(() => xRequestCall.hostUrl)
	const historyRq = computed<xRequestHistoryType[]>(() => xRequestCall.history)

	function mockLogin() {
		userStore.setLogin('demo-token-888', {
			naicename: '演示用户',
			avatar: '',
			id: '1',
			tags: ['pinia'] as string[],
			level: 1
		} as UseInfo)
	}

	function mockLogout() {
		userStore.loginOut()
	}

	async function testRq() : Promise<LISTPAGEART_TYPE[]> {
		const result = await api.getArtListArt<LISTPAGEART_TYPE>({
			page: 1,
			count: 16
		})
		return result
	}

	function setGloabalHostUrl() {
		xRequest.setHostUrl("https://mockapi.eolink.com/SZ2MLNR496b4c667257afac7776f24aea84c907abb261c1")
	}

	function clearGloabalHostUrl() {
		xRequest.setHostUrl("")
	}

	function openDev() {
		xRequest.setDev(true)
	}

	function request() {
		const rq = xRequest.getInstance()
		// 单例上的监听是全局的,演示前先清空,避免重复点击叠加
		rq.clearEventListener()
		rq.addEventListener('before', (opts) => {
			opts.header = { biubiu: "888" }
			opts.data = { sdf: "00" }
			status.value = 'before'
			return Promise.resolve(opts)
		})
			.addEventListener('before', (_opts) => {
				status.value = 'before-2'
				return new Promise((res) => {
					setTimeout(function () {
						res(true)
					}, 800)
				})
			})
			.addEventListener('abort', (_result) => {
				status.value = 'abort'
				return Promise.resolve(true)
			})
			.addEventListener('timeout', (_result) => {
				status.value = 'timeout'
				return Promise.resolve(true)
			})
			.addEventListener('error', (_result) => {
				status.value = 'error'
				return Promise.resolve(true)
			})
			.addEventListener('after', (result) => {
				status.value = 'after'
				return new Promise((res) => {
					setTimeout(function () {
						result.data = { code: 2, msg: "成功", data: "我是after修改后的结果" }
						res(result)
					}, 800)
				})
			})
			.addEventListener('complete', (_result) => {
				status.value = 'complete'
				return Promise.resolve(true)
			})

		rq.request<any>({ url: "/web/atcilDetail/", data: { id: 2 } })
			.then((result : xRequestResult<any>) => {
				resulte.value = result
				console.log(result, "请求成功的数据", resulte.value)
			})
			.catch((er) => {
				console.error(er, "请求错误")
			})
	}
</script>
最近更新