UNPKG

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
# 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); }; } ```