查看: 99|回复: 1

Vue大批量接口请求优化实战:并发池、缓存、虚拟列表与异常处理

[复制链接]
发表于 1 小时前 | 显示全部楼层 |阅读模式
在Vue项目中,大批量接口请求(如列表批量查询、批量提交、多模块初始化)常导致请求阻塞、页面卡顿、接口超时或服务器压力过大。本文基于Vue2/Vue3实战,从请求管控、缓存策略、代码优化、异常处理四个维度提供可落地的方案,覆盖核心场景与避坑要点。

## 一、核心痛点
大批量接口请求的核心问题集中在四点:
- 请求并发过多:超出浏览器同域名并发限制(Chrome为6个),导致请求排队阻塞。
- 重复请求浪费:同一接口短时间内多次发起,增加服务器开销。
- 数据处理低效:频繁修改响应式数据触发多次渲染,页面卡顿。
- 异常处理缺失:单个请求失败导致整体中断,缺乏超时和重试机制。

## 二、请求层面优化
### 1. 限制并发请求数量
通过并发池控制同时发起的请求数,避免超出浏览器限制。以下通用函数适配Vue2/Vue3:
  1. // utils/requestPool.js
  2. export function requestPool(requests, limit = 6) {
  3.   let index = 0;
  4.   const results = [];
  5.   let resolveAll;
  6.   const allPromise = new Promise(resolve => { resolveAll = resolve; });
  7.   async function run() {
  8.     if (index >= requests.length) {
  9.       resolveAll(results);
  10.       return;
  11.     }
  12.     const request = requests[index];
  13.     index++;
  14.     try {
  15.       const res = await request();
  16.       results.push({ success: true, data: res, index: index - 1 });
  17.     } catch (err) {
  18.       results.push({ success: false, error: err, index: index - 1 });
  19.     }
  20.     run();
  21.   }
  22.   for (let i = 0; i < limit; i++) { run(); }
  23.   return allPromise;
  24. }
复制代码
使用示例:
  1. import { requestPool } from '@/utils/requestPool';
  2. import { getListData } from '@/api/list';
  3. const requestList = [1,2,3,...,50].map(id => () => getListData(id));
  4. requestPool(requestList, 5).then(results => {
  5.   const successData = results.filter(item => item.success).map(item => item.data);
  6.   const failIds = results.filter(item => !item.success).map(item => item.index + 1);
  7. });
复制代码
并发数建议设为4-6,匹配浏览器限制。

### 2. 合并请求,减少接口调用次数
对于批量查询/提交场景,避免循环单个请求,改为一次携带多个参数。需与后端约定批量接口参数格式(如逗号分隔id或数组)。
优化前(低效):
  1. const ids = [1,2,...,20];
  2. const list = [];
  3. for (const id of ids) {
  4.   const res = await getGoodsDetail(id);
  5.   list.push(res.data);
  6. }
复制代码
优化后:
  1. const ids = [1,2,...,20];
  2. const res = await getBatchGoodsDetail({ ids: ids.join(',') });
  3. const list = res.data;
复制代码
若批量数据过多(如超过100条),可拆分多个合并请求(每50条一次),避免参数过长。

### 3. 取消无效请求
页面切换或组件销毁时,取消未完成的请求,避免资源浪费和数据错乱。结合Axios与Vue3生命周期:
  1. // utils/request.js
  2. import axios from 'axios';
  3. const CancelToken = axios.CancelToken;
  4. let cancel;
  5. export function request(config) {
  6.   return axios({
  7.     ...config,
  8.     cancelToken: new CancelToken(c => { cancel = c; })
  9.   });
  10. }
  11. export function cancelRequest(msg = '请求已取消') {
  12.   if (cancel) {
  13.     cancel(msg);
  14.     cancel = null;
  15.   }
  16. }
  17. // 组件中(Vue3)
  18. import { onUnmounted } from 'vue';
  19. import { request, cancelRequest } from '@/utils/request';
  20. export default {
  21.   setup() {
  22.     onUnmounted(() => { cancelRequest('组件已销毁'); });
  23.     const fetchData = async () => {
  24.       try {
  25.         const res = await request({ url: '/api/batch/data', method: 'get' });
  26.         // 处理数据
  27.       } catch (err) {
  28.         if (axios.isCancel(err)) {
  29.           console.log('请求已取消', err.message);
  30.           return;
  31.         }
  32.         // 其他错误处理
  33.       }
  34.     };
  35.     return { fetchData };
  36.   }
  37. };
复制代码
Vue2在beforeDestroy中调用cancelRequest。

## 三、缓存层面优化
### 1. 内存缓存(Pinia/Vuex)
适合单页面会话内复用不常变化的数据。Pinia示例:
  1. // store/modules/dataCache.js
  2. import { defineStore } from 'pinia';
  3. import { getBatchData } from '@/api/data';
  4. export const useDataCacheStore = defineStore('dataCache', {
  5.   state: () => ({ batchDataCache: new Map() }),
  6.   actions: {
  7.     async fetchBatchData(ids) {
  8.       const cacheKey = ids.join(',');
  9.       if (this.batchDataCache.has(cacheKey)) {
  10.         return this.batchDataCache.get(cacheKey);
  11.       }
  12.       const res = await getBatchData({ ids: cacheKey });
  13.       this.batchDataCache.set(cacheKey, res.data);
  14.       setTimeout(() => { this.batchDataCache.delete(cacheKey); }, 5 * 60 * 1000);
  15.       return res.data;
  16.     }
  17.   }
  18. });
  19. // 组件中
  20. import { useDataCacheStore } from '@/store/modules/dataCache';
  21. const store = useDataCacheStore();
  22. const data = await store.fetchBatchData([1,2,3]);
复制代码

### 2. 本地存储缓存(localStorage)
适合跨会话的字典表、分类列表等。注意设置过期时间,避免数据过时:
  1. export const cacheUtil = {
  2.   setCache(key, value, expire = 24*60*60*1000) {
  3.     const data = { data: value, expireTime: Date.now() + expire };
  4.     localStorage.setItem(key, JSON.stringify(data));
  5.   },
  6.   getCache(key) {
  7.     const str = localStorage.getItem(key);
  8.     if (!str) return null;
  9.     const data = JSON.parse(str);
  10.     if (Date.now() > data.expireTime) {
  11.       localStorage.removeItem(key);
  12.       return null;
  13.     }
  14.     return data.data;
  15.   }
  16. };
  17. // 使用
  18. import { cacheUtil } from '@/utils/cacheUtil';
  19. const cacheData = cacheUtil.getCache('dict_batch_data');
  20. if (cacheData) return cacheData;
  21. const res = await getDictList({ type: 'all' });
  22. cacheUtil.setCache('dict_batch_data', res.data, 24*60*60*1000);
复制代码

## 四、代码渲染优化
### 1. 批量修改响应式数据
避免循环修改响应式数据触发多次渲染,应一次性赋值:
  1. // 优化前:每次push触发渲染
  2. const list = reactive([]);
  3. results.forEach(item => list.push(item.data));
  4. // 优化后:先收集到普通数组,再一次性赋值
  5. const tempList = [];
  6. results.forEach(item => tempList.push(item.data));
  7. list.push(...tempList); // 仅触发一次渲染
复制代码
Vue2同样适用,使用Vue.set时也建议一次性修改。

### 2. 虚拟列表渲染
当数据量超过1000条时,使用vue-virtual-scroller只渲染可视区域:
  1. <template>
  2.   <virtual-scroller class="virtual-list" :items="batchData" :item-height="60" key-field="id">
  3.     <template #default="{ item }">
  4.       <div class="list-item">{{ item.name }}</div>
  5.     </template>
  6.   </virtual-scroller>
  7. </template>
  8. <script setup>
  9. import { ref } from 'vue';
  10. import { VirtualScroller } from 'vue-virtual-scroller';
  11. import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
  12. const batchData = ref([]);
  13. const fetchData = async () => {
  14.   const res = await getBatchData({ page: 1, size: 1000 });
  15.   batchData.value = res.data;
  16. };
  17. fetchData();
  18. </script>
  19. <style scoped>
  20. .virtual-list { height: 500px; overflow-y: auto; }
  21. .list-item { height: 60px; line-height: 60px; }
  22. </style>
复制代码
Vue2使用vue-virtual-scroller旧版本,配置类似。

### 3. 防抖节流避免重复请求
搜索筛选场景使用防抖:
  1. export function debounce(fn, delay = 300) {
  2.   let timer = null;
  3.   return function(...args) {
  4.     clearTimeout(timer);
  5.     timer = setTimeout(() => fn.apply(this, args), delay);
  6.   };
  7. }
  8. // 组件中
  9. const handleSearch = debounce(async (keyword) => {
  10.   const res = await getBatchSearch({ keyword, size: 50 });
  11.   // 处理数据
  12. }, 300);
复制代码

## 五、异常处理优化
### 1. 超时控制
在Axios全局或单次请求设置超时:
  1. import axios from 'axios';
  2. axios.defaults.timeout = 12000; // 全局12秒
  3. // 单次
  4. const res = await axios({ url: '/api/batch/data', method: 'get', params, timeout: 15000 });
复制代码

### 2. 失败重试
封装重试机制:
  1. export async function requestWithRetry(config, retryCount = 2) {
  2.   try {
  3.     return await axios(config);
  4.   } catch (err) {
  5.     if (retryCount > 0) {
  6.       console.log(`剩余重试次数:${retryCount}`);
  7.       return requestWithRetry(config, retryCount - 1);
  8.     }
  9.     throw new Error('请求失败,请检查网络');
  10.   }
  11. }
  12. // 使用
  13. const res = await requestWithRetry({ url: '/api/batch/data', params: { ids: '1,2,3' } }, 2);
复制代码

### 3. 部分失败处理
结合并发池,区分成功与失败项,单独处理失败项并支持重试:
  1. requestPool(requestList, 5).then(results => {
  2.   const successData = [];
  3.   const failList = [];
  4.   results.forEach((item, index) => {
  5.     if (item.success) successData.push(item.data);
  6.     else failList.push({ id: index + 1, error: item.error.message });
  7.   });
  8.   ElMessage.success(`成功${successData.length}条,失败${failList.length}条`);
  9.   if (failList.length > 0) {
  10.     console.log('失败详情:', failList);
  11.     // 可选自动重试
  12.     const failRequests = failList.map(item => () => requestList[item.id - 1]());
  13.     requestPool(failRequests, 2).then(/* 处理重试结果 */);
  14.   }
  15. });
复制代码

## 六、Vue2与Vue3差异
- 响应式:Vue3使用reactive/ref,可直接用push批量赋值;Vue2用Vue.set,同样推荐一次性修改。
- 状态管理:Vue3推荐Pinia,Vue2使用Vuex。
- 生命周期:Vue3用onUnmounted取消请求,Vue2用beforeDestroy。
- 虚拟列表:Vue3使用vue-virtual-scroller@next,Vue2使用旧版本。

## 七、总结与落地清单
核心优化动作:
1. 请求管控:并发池限制4-6个,合并批量请求,组件销毁时取消请求。
2. 缓存复用:高频不变用Pinia/Vuex内存缓存,长期不变用localStorage加过期时间。
3. 渲染优化:批量数据先存普通数组再一次性赋值,1000+条用虚拟列表。
4. 异常处理:设置超时10-15秒,失败重试2次,部分失败单独处理不中断。
5. 交互优化:搜索筛选加防抖300ms。
6. 版本适配:按Vue2/Vue3选择对应的生命周期和库版本。

以上方案需与后端配合(提供批量接口、优化响应速度),前端优化与后端结合才能最大提升大批量接口请求的体验。
回复

使用道具 举报

发表于 1 小时前 | 显示全部楼层

Re: Vue大批量接口请求优化实战:并发池、缓存、虚拟列表与异常处理

感谢分享,很系统的实战总结!关于并发池的实现,有个细节想请教:当请求数量很大时(比如上千个),递归调用的 `run` 函数会不会有栈溢出的风险?另外,内存缓存部分提到了 Pinia/Vuex,但缓存失效策略(比如定时清除、根据接口参数动态更新)有没有推荐的做法?期待后续能补充一下缓存与响应式数据同步的注意事项。
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册

本版积分规则

指导单位

江苏省公安厅

江苏省通信管理局

浙江省台州刑侦支队

DEFCON GROUP 86025

Hacking Group 021A

旗下站点

态势感知中心

应急响应中心

红盟安全

联系我们

官方QQ群:112851260

官方邮箱:security#ihonker.org(#改成@)

官方核心成员

关注微信公众号

Archiver|手机版|小黑屋| ( 沪ICP备2021026908号 )

GMT+8, 2026-6-12 18:55 , Processed in 0.032098 second(s), 18 queries , Gzip On, Redis On.

Powered by ihonker.com

Copyright © 2015-现在.

  • 返回顶部