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 中导入使用,全局生效。
| 函数 | 参数 | 说明 |
|---|---|---|
| getInstance | opts?: xRequestOptions | null | 取全局唯一实例。传入 opts 时作为单例默认请求参数。 |
| request | opts?: xRequestOptions | null | 走单例发请求,等价于 getInstance().request(opts),返回 Promise<xRequestResult<T>> |
| setAuth | isPass: boolean | 设为 false 后,所有新发起的请求都会被阻止,包括跨页面。 |
| setHeader | header: UTSJSONObject | null | 设置后所有新请求都会带上。实例或单次请求里的 header 会与它合并。 |
| setHostUrl | url: string | null | 设置后全局生效。单次请求配置了 hostUrl 会覆盖全局。 |
| setDev | dev: boolean | 开启后统计并收集整个应用的请求记录。 |
| getHistory | - | 返回应用运行期间的请求日志。 |
| setShowToast | showLoadToast = true, showSuccessToast = true, showErrorToast = true | 设置全局 toast 行为,单次请求可覆盖。 |
实例方法
new xRequest() / xRequest.getInstance() 拿到的是同一个实例。
| 方法 | 类型 | 说明 |
|---|---|---|
| setOptions | opts?: xRequestOptions | null | 设置单例默认请求参数。header 会与全局 setHeader 合并,不改写进行中的请求。 |
| addEventListener | event: xRequestEventType, fun | 添加全局事件监听,对后续所有请求生效。可重复添加,相同事件会一并执行。 |
| clearEventListener | event?: xRequestEventType | null | 移除监听。不传 event 则清空全部。 |
| abort | - | 中断当前进行中的全部请求。返回 boolean:至少中断一条为 true。 |
| request | opts?: xRequestOptions | null | 发起请求,返回 Promise<xRequestResult<T>> |
监听挂在单例上,是全局的。演示页在添加监听前会先 clearEventListener(),避免重复点击叠加。
addEventListener 说明
事件名
| 请求前 | 请求成功后改写结果 | 中断 | 超时 | 错误 | 授权被中止 | 成功 | 完成 |
|---|---|---|---|---|---|---|---|
before | after | abort | timeout | error | auth | success | complete |
回调签名
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>