uniapp-use-request
Version:
一个功能强大且高度可配置的 Vue 3 Composition API 请求 Hook,灵感来源于 [ahooks](https://ahooks.js.org/hooks/request/use-request) 和 [SWR](https://swr.vercel.app/)。它内置了缓存、防抖、节流、轮询、重试、请求队列和并发控制等特性。
498 lines (400 loc) • 16.6 kB
Markdown
# useRequest Hook(uniapp vue3)
一个功能强大且高度可配置的 Vue 3 Composition API 请求 Hook,灵感来源于 [ahooks](https://ahooks.js.org/hooks/request/use-request) 和 [SWR](https://swr.vercel.app/)。它内置了缓存、防抖、节流、轮询、重试、请求队列和并发控制等特性。
> 注意:如您下载使用本插件,请注意导入路径
## 特性
- 🚀 **开箱即用**:仅需一个 URL 即可发起请求。
- ✨ **TypeScript 支持**:提供完整的类型定义,保障代码健壮性。
- 🔄 **自动请求与手动控制**:支持组件挂载时自动请求,也支持手动触发。
- 🧠 **状态管理**:自动管理 `loading`, `data`, `error` 等响应式状态。
- 💾 **缓存**:内置请求缓存,可自定义缓存时间,避免重复请求。
- ⏳ **防抖 & 节流**:轻松处理高频触发的请求场景。
- polling **轮询**:支持轮询,可手动开始或停止。
- 🔗 **依赖请求**:支持根据响应式状态的变化来决定是否发起请求。
- ♻️ **错误重试**:内置请求失败后的重试机制。
- 🎏 **拦截器**:支持请求、响应和错误拦截器,轻松实现全局逻辑处理(如 Token 添加、统一错误上报等)。
- 🔌 **插件化**:核心逻辑可通过插件扩展。
- 🛑 **请求取消**:可以取消正在进行的请求。
- 🚦 **请求队列与并发控制**:管理请求的执行顺序和并发数量。
---
## 快速上手
```bash
npm i uniapp-use-request
```
## 更新日志
### v1.0.7
- 添加了LRU内存缓存类,实现复杂情况更快访问内存数据
### v1.0.6
- 修复了响应拦截器未生效
### v1.0.5
- 修复了`headers`参数未生效问题
### v1.0.4
- 优化数据缓存问题,`Map` 改为 `Storage`;网络请求失败以后会使用本地缓存数据 统一存储到`__USE_REQUEST_CACHE__`
### v1.0.3
- 修复了 cacheTime 设置数据缓存无效问题,在一定时间内拦截接口返回同样数据
### v1.0.2
- 更新了 data 作为 `RequestOptions` 参数进行处理,params 和 data 目前行为保持一致
### 1. 全局配置与拦截器 (在 `App.vue` 中)
这是使用 `useRequest` 的第一步,也是最重要的一步。在应用的入口处(通常是 `App.vue`)进行全局配置。
```typescript
// App.vue
import {
setConfig,
setBaseURL,
addInterceptor,
createRequestInterceptor,
createResponseInterceptor,
createErrorInterceptor,
} from "uniapp-use-request";
// 1. 设置全局基础 URL
setBaseURL("https://api.example.com");
// 2. 设置全局默认配置
setConfig({
timeout: 15000,
showLoading: true, // 默认显示 uni.showLoading
loadingText: "加载中...",
showError: true, // 默认显示 uni.showToast
retryCount: 1, // 失败后默认重试1次
});
// 3. 添加请求拦截器
addInterceptor(
createRequestInterceptor((config) => {
console.log("[useRequest] 请求拦截器: ", config);
// 自动添加 Token
if (config.withToken) {
const token = uni.getStorageSync("token");
if (token) {
config.headers = {
...config.headers,
Authorization: `Bearer ${token}`,
};
}
}
return config;
})
);
// 4. 添加响应拦截器
addInterceptor(
createResponseInterceptor((response, config) => {
console.log("[useRequest] 响应拦截器: ", response);
// 这里可以对响应数据进行统一处理
// 例如,根据后端的业务码判断请求是否成功
const res = response as any;
if (res.code !== 200) {
// 抛出错误,会进入错误拦截器
throw new Error(res.message || "业务请求失败");
}
// 只返回 data 字段
return res.data;
})
);
// 5. 添加错误拦截器
addInterceptor(
createErrorInterceptor((error, config) => {
console.error("[useRequest] 错误拦截器: ", error);
// 这里可以对错误进行统一处理,例如上报错误日志、弹窗提示等
if (config.showError) {
uni.showToast({
title: error.message || "请求异常",
icon: "none",
});
}
// 返回错误对象,如果想在业务代码中继续处理错误
return error;
})
);
```
### 2. 在页面或组件中使用
#### 默认自动请求
`useRequest` 默认在组件挂载后自动发起请求。
```typescript
// pages/some-page/index.vue
import { useRequest } from "uniapp-use-request";
import { watch } from "vue";
const { data, loading, error } = useRequest<User>("/api/user/profile");
watch(data, (newData) => {
if (newData) {
console.log("获取到用户信息:", newData);
}
});
import { useRequest } from "uniapp-use-request";
// 如果这里使用data传参的话,请给data赋别名 否则会冲突!!!
const {
data: otherData,
loading,
error,
} = useRequest("technew/index/indexConfig", {
data: {
type: "login",
},
method: "POST",
});
```
#### 手动触发请求
设置 `manual: true`,请求将不会自动发起,需要手动调用 `run` 方法。
```typescript
const { loading, run: login } = useRequest("/api/login", {
manual: true,
method: "POST",
});
const handleLogin = async () => {
try {
const result = await login({ username: "test", password: "123" });
console.log("登录成功:", result);
} catch (e) {
console.error("登录失败:", e);
}
};
```
---
## 高级用法
### 缓存
设置 `cacheTime`,在指定时间内,同样的请求将直接返回缓存数据。
```typescript
const { data, loading } = useRequest("/api/articles", {
cacheTime: 5 * 60 * 1000, // 缓存5分钟
});
// 手动清除该请求的缓存
const { clearCache } = useRequest("/api/articles");
clearCache();
```
### 防抖与节流
适用于搜索等高频触发场景。
```typescript
// 防抖:输入停止后 500ms 才发起请求
const { run: search } = useRequest("/api/search", {
manual: true,
debounce: true,
debounceTime: 500,
});
search();
// 节流:每 500ms 最多发起一次请求
const { run: submit } = useRequest("/api/submit", {
manual: true,
throttle: true,
throttleTime: 500,
});
submit();
```
### 轮询
通过 `pollingInterval` 设置轮询间隔。
```typescript
const { data, polling, startPolling, stopPolling } = useRequest("/api/status", {
pollingInterval: 3000, // 每3秒轮询一次
pollingMaxCount: 10, // 最多轮询10次
});
// 手动停止
stopPolling();
// 手动开始
startPolling();
```
### 请求依赖 (`ready`)
当 `ready` 的值为 `true` 时,请求才会发起。`ready` 可以是一个 `ref`。
```typescript
import { ref, computed } from "vue";
const userId = ref("");
const { data } = useRequest("/api/user/detail", {
// 使用 computed 确保 ready 是响应式的
ready: computed(() => !!userId.value),
params: {
id: userId, // useRequest 会自动解包 ref
},
});
// 在获取到 userId 后,请求会自动发出
userId.value = "user-123";
```
### 请求取消
可以手动取消一个正在进行的请求。
```typescript
const { run, cancel, loading } = useRequest("/api/long-request", {
manual: true,
});
run();
// 3秒后如果请求还未完成,则取消它
setTimeout(() => {
if (loading.value) {
cancel();
}
}, 3000);
```
### 请求队列与并发控制
```javascript
// 请求2: 获取提现列表 (高优先级)
const { data: userInfo, run } = useRequest("technew/my.Index/withdrawList", {
manual: true,
requestId: "user-init",
priority: 10, // 高优先级
method: "POST",
});
// 请求1: 获取登录配置 (低优先级)
const { data: loginConfig, run: getConfig } = useRequest(
"technew/index/indexConfig",
{
manual: true,
requestId: "user-init",
priority: 1, // 低优先级
params: { type: "login" },
}
);
// 同时触发两个请求,它们将根据优先级排队执行
getConfig();
run();
```
---
## API 参考
### `useRequest` 参数
| 参数 | 说明 | 类型 | 默认值 |
| ------- | ------------------ | -------------------------------------- | ------ |
| url | 请求地址或请求函数 | `string \| ((params?: any) => string)` | - |
| options | 请求配置 | `RequestOptions` | `{}` |
### `RequestOptions` (完整参数)
| 参数 | 说明 | 类型 | 默认值 |
| :---------------- | :--------------------------------------------------------- | :------------------------ | :------------ |
| `manual` | 是否手动触发请求,`true` 则首次不请求 | `boolean` | `false` |
| `params` | 请求参数,`GET`请求时会自动拼接到 URL,`POST`时作为 `data` | `Record<string, any>` | `{}` |
| `data` | 请求体数据,通常用于 `POST`, `PUT` 请求 | `any` | - |
| `method` | 请求方法 | `HttpMethod` | `'POST'` |
| `headers` | 自定义请求头 | `Record<string, string>` | - |
| `timeout` | 请求超时时间 (ms) | `number` | `30000` |
| `cacheTime` | 缓存时间 (ms),`0` 表示不缓存 | `number` | `0` |
| `retryCount` | 失败后重试次数 | `number` | `0` |
| `retryInterval` | 失败重试间隔时间 (ms) | `number` | `1000` |
| `pollingInterval` | 轮询间隔 (ms),`0` 表示不轮询 | `number` | `0` |
| `pollingMaxCount` | 轮询最大次数,`0` 表示无限次 | `number` | `0` |
| `debounce` | 是否开启防抖 | `boolean` | `false` |
| `debounceTime` | 防抖时间 (ms) | `number` | `300` |
| `throttle` | 是否开启节流 | `boolean` | `false` |
| `throttleTime` | 节流时间 (ms) | `number` | `300` |
| `ready` | 依赖请求,`true` 时才会发起请求,支持 `Ref<boolean>` | `boolean \| Ref<boolean>` | `true` |
| `withToken` | 请求头中是否携带 Token | `boolean` | `true` |
| `showLoading` | 是否显示全局 `uni.showLoading` | `boolean` | `false` |
| `loadingText` | `showLoading` 为 `true` 时的提示文本 | `string` | `'加载中...'` |
| `showError` | 是否在错误拦截器中显示 `uni.showToast` | `boolean` | `true` |
| `requestId` | 请求队列 ID,相同 ID 会进入同一队列,按优先级执行 | `string` | - |
| `priority` | 请求优先级,在队列中,数字越大优先级越高 | `number` | `0` |
### `useRequest` 返回值
| 参数 | 说明 | 类型 |
| -------------- | -------------------- | ------------------------- |
| `data` | 响应数据 | `Ref<T \| undefined>` |
| `loading` | 是否正在请求 | `Ref<boolean>` |
| `error` | 错误对象 | `Ref<Error \| null>` |
| `polling` | 是否正在轮询 | `Ref<boolean>` |
| `run` | 手动触发请求的函数 | `(...args) => Promise<T>` |
| `cancel` | 取消当前请求的函数 | `() => void` |
| `refresh` | 使用上次参数重新请求 | `() => Promise<T>` |
| `clearCache` | 清除当前请求的缓存 | `() => void` |
| `startPolling` | 开始轮询 | `() => void` |
| `stopPolling` | 停止轮询 | `() => void` |
---
## 完整使用示例 (场景化)
> **请注意**:以下代码旨在提供 `useRequest` hook 的各种使用场景示例,不可直接运行。正确的用法是将 `useRequest` 导入到你的 Vue 组件的 `<script setup>` 中使用。
```typescript
import { useRequest } from "./index";
import { ref, watch, computed, Ref } from "vue";
// 定义一些可能用到的类型
type UserInfo = {
userId: string;
username: string;
avatar: string;
};
type Article = {
id: number;
title: string;
content: string;
};
// --- 示例场景 ---
/**
* 场景1: 基础用法 - 自动获取用户信息
* 组件挂载后,自动请求并展示用户信息。
*/
function useUserProfile() {
const {
data: userProfile,
loading,
error,
} = useRequest<UserInfo>("/api/user/profile");
// 使用 watch 监听数据变化
watch(userProfile, (newUser) => {
if (newUser) {
console.log("用户信息已更新:", newUser.username);
}
});
// 在模板中可以直接使用 userProfile, loading, error
// <view v-if="loading">加载中...</view>
// <view v-if="userProfile">欢迎, {{ userProfile.username }}</view>
}
/**
* 场景2: 手动触发 - 登录操作
* 点击按钮后,手动调用 `run` 方法来发起登录请求。
*/
function useLogin() {
const loginForm = ref({
username: "test",
password: "123",
});
const { run: performLogin, loading: isLoggingIn } = useRequest(
"/api/auth/login",
{
manual: true,
method: "POST",
}
);
const onLoginClick = async () => {
try {
// run 方法可以接受参数,这些参数会作为请求的 data
const token = await performLogin(loginForm.value);
uni.setStorageSync("token", token);
uni.showToast({ title: "登录成功" });
} catch (e) {
// 错误已由全局错误拦截器处理,这里可以不写
console.error("登录失败,具体原因已由拦截器提示。");
}
};
// 在模板中绑定 onLoginClick 到按钮上
// <button @click="onLoginClick" :loading="isLoggingIn">登录</button>
}
/**
* 场景3: 依赖请求 - 编辑时获取文章详情
* 只有当文章ID存在时,才发起请求获取详情。
*/
function useArticleDetail(articleId: Ref<number | null>) {
const { data: article, loading } = useRequest<Article>(
(id) => `/api/article/${id}`, // URL 可以是一个函数
{
// 当 `articleId` 有有效值时,`ready` 才为 true
ready: computed(() => !!articleId.value),
}
);
// 当外部的 articleId 变化时,如果它不为 null,请求会自动发起
// 例如:
// articleId.value = 123; // 这会触发请求
}
/**
* 场景4: 缓存 - 获取城市列表
* 城市列表这类不经常变动的数据,非常适合使用缓存。
*/
function useCityList() {
const { data: cityList, loading } = useRequest("/api/cities", {
cacheTime: 10 * 60 * 1000, // 缓存10分钟
});
// 在10分钟内,多次调用 useCityList 不会发出新的网络请求
}
/**
* 场景5: 取消请求
* 演示如何取消一个正在进行的请求。
*/
function useCancellableRequest() {
const { run, cancel, loading } = useRequest("/api/slow-request", {
manual: true,
});
const startRequest = () => {
run();
// 模拟一个场景:如果请求在2秒内没有完成,就自动取消它
setTimeout(() => {
if (loading.value) {
console.log("请求超时,正在取消...");
cancel();
}
}, 2000);
};
}
```