在Vue项目中,大批量接口请求(如列表批量查询、批量提交、多模块初始化)常导致请求阻塞、页面卡顿、接口超时或服务器压力过大。本文基于Vue2/Vue3实战,从请求管控、缓存策略、代码优化、异常处理四个维度提供可落地的方案,覆盖核心场景与避坑要点。
## 一、核心痛点
大批量接口请求的核心问题集中在四点:
- 请求并发过多:超出浏览器同域名并发限制(Chrome为6个),导致请求排队阻塞。
- 重复请求浪费:同一接口短时间内多次发起,增加服务器开销。
- 数据处理低效:频繁修改响应式数据触发多次渲染,页面卡顿。
- 异常处理缺失:单个请求失败导致整体中断,缺乏超时和重试机制。
## 二、请求层面优化
### 1. 限制并发请求数量
通过并发池控制同时发起的请求数,避免超出浏览器限制。以下通用函数适配Vue2/Vue3:- // utils/requestPool.js
- export function requestPool(requests, limit = 6) {
- let index = 0;
- const results = [];
- let resolveAll;
- const allPromise = new Promise(resolve => { resolveAll = resolve; });
- async function run() {
- if (index >= requests.length) {
- resolveAll(results);
- return;
- }
- const request = requests[index];
- index++;
- try {
- const res = await request();
- results.push({ success: true, data: res, index: index - 1 });
- } catch (err) {
- results.push({ success: false, error: err, index: index - 1 });
- }
- run();
- }
- for (let i = 0; i < limit; i++) { run(); }
- return allPromise;
- }
复制代码 使用示例:- import { requestPool } from '@/utils/requestPool';
- import { getListData } from '@/api/list';
- const requestList = [1,2,3,...,50].map(id => () => getListData(id));
- requestPool(requestList, 5).then(results => {
- const successData = results.filter(item => item.success).map(item => item.data);
- const failIds = results.filter(item => !item.success).map(item => item.index + 1);
- });
复制代码 并发数建议设为4-6,匹配浏览器限制。
### 2. 合并请求,减少接口调用次数
对于批量查询/提交场景,避免循环单个请求,改为一次携带多个参数。需与后端约定批量接口参数格式(如逗号分隔id或数组)。
优化前(低效):- const ids = [1,2,...,20];
- const list = [];
- for (const id of ids) {
- const res = await getGoodsDetail(id);
- list.push(res.data);
- }
复制代码 优化后:- const ids = [1,2,...,20];
- const res = await getBatchGoodsDetail({ ids: ids.join(',') });
- const list = res.data;
复制代码 若批量数据过多(如超过100条),可拆分多个合并请求(每50条一次),避免参数过长。
### 3. 取消无效请求
页面切换或组件销毁时,取消未完成的请求,避免资源浪费和数据错乱。结合Axios与Vue3生命周期:- // utils/request.js
- import axios from 'axios';
- const CancelToken = axios.CancelToken;
- let cancel;
- export function request(config) {
- return axios({
- ...config,
- cancelToken: new CancelToken(c => { cancel = c; })
- });
- }
- export function cancelRequest(msg = '请求已取消') {
- if (cancel) {
- cancel(msg);
- cancel = null;
- }
- }
- // 组件中(Vue3)
- import { onUnmounted } from 'vue';
- import { request, cancelRequest } from '@/utils/request';
- export default {
- setup() {
- onUnmounted(() => { cancelRequest('组件已销毁'); });
- const fetchData = async () => {
- try {
- const res = await request({ url: '/api/batch/data', method: 'get' });
- // 处理数据
- } catch (err) {
- if (axios.isCancel(err)) {
- console.log('请求已取消', err.message);
- return;
- }
- // 其他错误处理
- }
- };
- return { fetchData };
- }
- };
复制代码 Vue2在beforeDestroy中调用cancelRequest。
## 三、缓存层面优化
### 1. 内存缓存(Pinia/Vuex)
适合单页面会话内复用不常变化的数据。Pinia示例:- // store/modules/dataCache.js
- import { defineStore } from 'pinia';
- import { getBatchData } from '@/api/data';
- export const useDataCacheStore = defineStore('dataCache', {
- state: () => ({ batchDataCache: new Map() }),
- actions: {
- async fetchBatchData(ids) {
- const cacheKey = ids.join(',');
- if (this.batchDataCache.has(cacheKey)) {
- return this.batchDataCache.get(cacheKey);
- }
- const res = await getBatchData({ ids: cacheKey });
- this.batchDataCache.set(cacheKey, res.data);
- setTimeout(() => { this.batchDataCache.delete(cacheKey); }, 5 * 60 * 1000);
- return res.data;
- }
- }
- });
- // 组件中
- import { useDataCacheStore } from '@/store/modules/dataCache';
- const store = useDataCacheStore();
- const data = await store.fetchBatchData([1,2,3]);
复制代码
### 2. 本地存储缓存(localStorage)
适合跨会话的字典表、分类列表等。注意设置过期时间,避免数据过时:- export const cacheUtil = {
- setCache(key, value, expire = 24*60*60*1000) {
- const data = { data: value, expireTime: Date.now() + expire };
- localStorage.setItem(key, JSON.stringify(data));
- },
- getCache(key) {
- const str = localStorage.getItem(key);
- if (!str) return null;
- const data = JSON.parse(str);
- if (Date.now() > data.expireTime) {
- localStorage.removeItem(key);
- return null;
- }
- return data.data;
- }
- };
- // 使用
- import { cacheUtil } from '@/utils/cacheUtil';
- const cacheData = cacheUtil.getCache('dict_batch_data');
- if (cacheData) return cacheData;
- const res = await getDictList({ type: 'all' });
- cacheUtil.setCache('dict_batch_data', res.data, 24*60*60*1000);
复制代码
## 四、代码渲染优化
### 1. 批量修改响应式数据
避免循环修改响应式数据触发多次渲染,应一次性赋值:- // 优化前:每次push触发渲染
- const list = reactive([]);
- results.forEach(item => list.push(item.data));
- // 优化后:先收集到普通数组,再一次性赋值
- const tempList = [];
- results.forEach(item => tempList.push(item.data));
- list.push(...tempList); // 仅触发一次渲染
复制代码 Vue2同样适用,使用Vue.set时也建议一次性修改。
### 2. 虚拟列表渲染
当数据量超过1000条时,使用vue-virtual-scroller只渲染可视区域:- <template>
- <virtual-scroller class="virtual-list" :items="batchData" :item-height="60" key-field="id">
- <template #default="{ item }">
- <div class="list-item">{{ item.name }}</div>
- </template>
- </virtual-scroller>
- </template>
- <script setup>
- import { ref } from 'vue';
- import { VirtualScroller } from 'vue-virtual-scroller';
- import 'vue-virtual-scroller/dist/vue-virtual-scroller.css';
- const batchData = ref([]);
- const fetchData = async () => {
- const res = await getBatchData({ page: 1, size: 1000 });
- batchData.value = res.data;
- };
- fetchData();
- </script>
- <style scoped>
- .virtual-list { height: 500px; overflow-y: auto; }
- .list-item { height: 60px; line-height: 60px; }
- </style>
复制代码 Vue2使用vue-virtual-scroller旧版本,配置类似。
### 3. 防抖节流避免重复请求
搜索筛选场景使用防抖:- export function debounce(fn, delay = 300) {
- let timer = null;
- return function(...args) {
- clearTimeout(timer);
- timer = setTimeout(() => fn.apply(this, args), delay);
- };
- }
- // 组件中
- const handleSearch = debounce(async (keyword) => {
- const res = await getBatchSearch({ keyword, size: 50 });
- // 处理数据
- }, 300);
复制代码
## 五、异常处理优化
### 1. 超时控制
在Axios全局或单次请求设置超时:- import axios from 'axios';
- axios.defaults.timeout = 12000; // 全局12秒
- // 单次
- const res = await axios({ url: '/api/batch/data', method: 'get', params, timeout: 15000 });
复制代码
### 2. 失败重试
封装重试机制:- export async function requestWithRetry(config, retryCount = 2) {
- try {
- return await axios(config);
- } catch (err) {
- if (retryCount > 0) {
- console.log(`剩余重试次数:${retryCount}`);
- return requestWithRetry(config, retryCount - 1);
- }
- throw new Error('请求失败,请检查网络');
- }
- }
- // 使用
- const res = await requestWithRetry({ url: '/api/batch/data', params: { ids: '1,2,3' } }, 2);
复制代码
### 3. 部分失败处理
结合并发池,区分成功与失败项,单独处理失败项并支持重试:- requestPool(requestList, 5).then(results => {
- const successData = [];
- const failList = [];
- results.forEach((item, index) => {
- if (item.success) successData.push(item.data);
- else failList.push({ id: index + 1, error: item.error.message });
- });
- ElMessage.success(`成功${successData.length}条,失败${failList.length}条`);
- if (failList.length > 0) {
- console.log('失败详情:', failList);
- // 可选自动重试
- const failRequests = failList.map(item => () => requestList[item.id - 1]());
- requestPool(failRequests, 2).then(/* 处理重试结果 */);
- }
- });
复制代码
## 六、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选择对应的生命周期和库版本。
以上方案需与后端配合(提供批量接口、优化响应速度),前端优化与后端结合才能最大提升大批量接口请求的体验。 |