【Vue3】大屏性能优化黑科技:Vue 3 中实现请求合并,让你的大屏飞起来!
2025年11月27日 18:03
前言
作为大屏开发者,你是否遇到过这样的困扰:一个复杂的大屏页面,多个组件同时请求同一个接口,导致浏览器在短时间内发起了 N 次相同的网络请求?这不仅拖慢了大屏的加载速度,还可能导致数据不一致。今天,我将分享一个专为大屏优化的黑科技——请求合并,让你的 Vue 3 大屏应用瞬间提升一个档次!
一、大屏场景:为什么需要请求合并?
想象一下这个场景:
你正在开发一个 工业监控大屏 ,页面上有多个组件需要展示实时生产数据:
- 顶部状态栏显示总生产量和合格率
- 左侧设备列表显示所有设备的运行状态
- 中间图表区域展示生产趋势图
- 右侧告警面板显示当前告警数量
当大屏加载时,这四个组件都会发起
GET /api/production/stats请求获取实时生产统计数据。如果没有请求合并,浏览器会在短时间内发起 4 次完全相同的网络请求!
在大屏应用中,这种情况会导致更严重的问题:
- 大屏加载缓慢 :多个请求同时发起,网络带宽被占用,导致大屏无法快速呈现
- 数据不同步 :不同组件接收到数据的时间不同,导致大屏数据不一致
- 服务器压力大 :大屏通常需要实时刷新,频繁的重复请求会给服务器带来巨大压力
- 影响实时性 :过多的网络请求会导致数据更新延迟,影响大屏的实时监控效果
二、解决方案:请求合并是什么?
请求合并 是一种专为大屏优化的性能技术,它的核心思想是:
当大屏上的 多个组件同时请求相同数据 时,只执行 一次实际的网络请求 ,然后将结果分发给所有等待的组件。
用一句话概括: 合并相同请求,共享实时数据 。
三、实现原理:如何为大屏实现请求合并?
大屏应用的请求合并实现需要考虑以下特点:
- 高频请求 :大屏通常需要频繁刷新数据
- 实时数据 :数据更新频率高,需要保证数据的时效性
- 多组件共享 :多个可视化组件需要相同的数据
- 性能敏感 :大屏对性能要求极高,需要快速响应
我们将使用 Map 来跟踪正在进行的请求,并使用 Promise 来实现结果的分发。
核心实现步骤:
- 生成请求唯一标识 :根据请求的 URL、方法、参数等生成唯一的请求键
- 检查请求状态 :检查是否有相同的请求正在进行
- 复用或发起请求 :如果有,直接复用;如果没有,发起新请求
- 分发请求结果 :将请求结果分发给所有等待的组件
- 清理请求跟踪 :请求完成后,从跟踪列表中移除
四、代码实现:Vue 3 + Axios 大屏请求合并
1. 核心代码:Axios 请求合并拦截器
// src/api/axios.js
import axios from 'axios';
// 创建axios实例
const instance = axios.create({
baseURL: '/api', // 大屏API基础地址
timeout: 5000, // 大屏请求超时时间,通常设置较短
headers: {
'Content-Type': 'application/json'
}
});
// 用于跟踪正在进行的请求
const pendingRequests = new Map();
// 生成请求唯一标识
function generateRequestKey(config) {
const { method, url, params, data } = config;
return `${method}_${url}_${JSON.stringify(params)}_${JSON.stringify(data)}`;
}
// 请求拦截器
instance.interceptors.request.use(
(config) => {
// 生成请求唯一标识
const requestKey = generateRequestKey(config);
// 检查是否有相同的请求正在进行
if (pendingRequests.has(requestKey)) {
// 如果有,返回正在进行的请求的Promise
return pendingRequests.get(requestKey);
}
// 创建一个新的Promise,用于跟踪请求状态
const requestPromise = new Promise((resolve, reject) => {
// 存储resolve和reject函数,供响应拦截器使用
config.resolve = resolve;
config.reject = reject;
});
// 将请求Promise存储到pendingRequests中
pendingRequests.set(requestKey, requestPromise);
config.requestKey = requestKey;
return config;
},
(error) => {
return Promise.reject(error);
}
);
// 响应拦截器
instance.interceptors.response.use(
(response) => {
const { config } = response;
// 从pendingRequests中获取请求Promise
const requestPromise = pendingRequests.get(config.requestKey);
if (requestPromise) {
// 从pendingRequests中移除
pendingRequests.delete(config.requestKey);
// 使用resolve函数完成Promise,分发结果给所有组件
if (config.resolve) {
config.resolve(response);
}
}
return response;
},
(error) => {
const { config } = error;
// 处理请求失败的情况
if (config && config.requestKey) {
// 从pendingRequests中移除
pendingRequests.delete(config.requestKey);
// 拒绝所有等待的请求
if (config.reject) {
config.reject(error);
}
}
return Promise.reject(error);
}
);
// 导出带请求合并的axios实例
export default instance;
2. 应用示例
<template>
<div class="app">
<h1>Vue 3 请求合并示例</h1>
<div class="controls">
<button @click="fetchMultipleRequests" :disabled="loading">
{{ loading ? '请求中...' : '同时发起3个相同请求' }}
</button>
<button @click="clearResults">清除结果</button>
</div>
<div class="results">
<h2>请求结果</h2>
<div
v-for="(result, index) in results"
:key="index"
class="result-item"
>
<p>请求 {{ index + 1 }}: {{ result.status }}</p>
<p v-if="result.success">数据: {{ JSON.stringify(result.data) }}</p>
<p v-else>错误: {{ result.error }}</p>
<p>耗时: {{ result.time }}ms</p>
</div>
<div class="summary" v-if="results.length > 0">
<h3>性能总结</h3>
<p>发起请求数: <strong>{{ results.length }}</strong></p>
<p>实际网络请求数: <strong>1</strong></p>
<p>减少请求数: <strong>{{ results.length - 1 }}</strong></p>
</div>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import axios from 'axios';
// 创建axios实例
const axiosInstance = axios.create({
timeout: 5000
});
// 用于跟踪正在进行的请求
const pendingRequests = new Map();
// 生成请求唯一标识
function generateRequestKey(config) {
const { method, url, params, data } = config;
return `${method}_${url}_${JSON.stringify(params)}_${JSON.stringify(data)}`;
}
// 请求拦截器 - 实现请求合并
axiosInstance.interceptors.request.use(
(config) => {
const requestKey = generateRequestKey(config);
if (pendingRequests.has(requestKey)) {
return pendingRequests.get(requestKey);
}
const requestPromise = new Promise((resolve, reject) => {
config.resolve = resolve;
config.reject = reject;
});
pendingRequests.set(requestKey, requestPromise);
config.requestKey = requestKey;
return config;
},
(error) => Promise.reject(error)
);
// 响应拦截器 - 分发结果
axiosInstance.interceptors.response.use(
(response) => {
const { config } = response;
const requestPromise = pendingRequests.get(config.requestKey);
if (requestPromise) {
pendingRequests.delete(config.requestKey);
if (config.resolve) {
config.resolve(response);
}
}
return response;
},
(error) => {
const { config } = error;
if (config && config.requestKey) {
pendingRequests.delete(config.requestKey);
if (config.reject) {
config.reject(error);
}
}
return Promise.reject(error);
}
);
// 状态管理
const results = ref([]);
const loading = ref(false);
// 同时发起多个请求
const fetchMultipleRequests = async () => {
loading.value = true;
results.value = [];
try {
// 同时发起3个相同的请求
const requests = Array(3).fill().map(async (_, index) => {
const startTime = Date.now();
try {
// 使用公共API进行测试
const response = await axiosInstance.get('https://jsonplaceholder.typicode.com/todos/1');
const endTime = Date.now();
return {
status: '成功',
success: true,
data: response.data,
time: endTime - startTime
};
} catch (error) {
const endTime = Date.now();
return {
status: '失败',
success: false,
error: error.message,
time: endTime - startTime
};
}
});
// 等待所有请求完成
const resultsList = await Promise.all(requests);
results.value = resultsList;
} finally {
loading.value = false;
}
};
// 清除结果
const clearResults = () => {
results.value = [];
};
</script>
<style scoped>
.app {
max-width: 600px;
margin: 0 auto;
padding: 20px;
font-family: Arial, sans-serif;
}
.controls {
margin: 20px 0;
}
button {
padding: 10px 20px;
margin-right: 10px;
background-color: #42b983;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 16px;
}
button:hover {
background-color: #3aa876;
}
button:disabled {
background-color: #ccc;
cursor: not-allowed;
}
.results {
margin-top: 30px;
}
.result-item {
margin: 10px 0;
padding: 15px;
border: 1px solid #eee;
border-radius: 4px;
background-color: #f9f9f9;
}
.summary {
margin-top: 20px;
padding: 15px;
background-color: #e8f5e8;
border-radius: 4px;
border-left: 4px solid #42b983;
}
</style>
五、扩展实现:支持 Fetch API
除了 Axios,我们还可以为 Fetch API 实现请求合并,以支持更多大屏场景:
// src/api/fetch.js
// 用于跟踪正在进行的fetch请求
const pendingRequests = new Map();
// 生成请求唯一标识
function generateFetchKey(url, options = {}) {
const { method = 'GET', headers, body } = options;
return `${method}_${url}_${JSON.stringify(headers)}_${body}`;
}
/**
* 带请求合并的fetch包装函数
*/
export const fetchWithMerge = async (url, options = {}) => {
// 生成请求唯一标识
const requestKey = generateFetchKey(url, options);
// 检查是否有相同的请求正在进行
if (pendingRequests.has(requestKey)) {
// 如果有,返回正在进行的请求的Promise
return pendingRequests.get(requestKey);
}
// 创建请求Promise
const requestPromise = (async () => {
try {
// 执行实际的fetch请求
const response = await fetch(url, options);
// 检查响应是否成功
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
// 返回响应
return response;
} finally {
// 无论请求成功还是失败,都从pendingRequests中移除
pendingRequests.delete(requestKey);
}
})();
// 将请求Promise存储到pendingRequests中
pendingRequests.set(requestKey, requestPromise);
// 返回请求Promise
return requestPromise;
};
// 简化的GET请求方法,适合大屏实时数据获取
export const getWithMerge = async (url, options = {}) => {
const response = await fetchWithMerge(url, { method: 'GET', ...options });
return response.json();
};
六、大屏效果对比:请求合并带来的性能提升
| 指标 | 未使用请求合并 | 使用请求合并 | 提升幅度 |
|---|---|---|---|
| 网络请求次数 | 5次 | 1次 | 80% |
| 数据传输量 | 5倍 | 1倍 | 80% |
| 服务器压力 | 5倍 | 1倍 | 80% |
| 大屏加载时间 | 取决于最慢的请求 | 取决于单次请求 | 显著提升 |
| 数据同步性 | 可能不同步 | 完全同步 | 100% |
七、大屏应用场景
请求合并特别适合以下大屏场景:
- 工业监控大屏:多个组件同时请求设备状态、生产数据等
- 城市管理大屏:多个组件同时请求人口、交通、环境等数据
- 金融监控大屏:多个组件同时请求股票、汇率、交易数据等
- 物流监控大屏:多个组件同时请求货物、车辆、仓库数据等
- 能源监控大屏:多个组件同时请求电力、水资源、燃气数据等
八、大屏优化注意事项
- 请求标识的唯一性:确保生成的请求标识能准确区分不同的大屏请求
- 请求失败的处理:确保请求失败时,所有等待的组件都能得到正确的错误信息
- 请求超时的处理:大屏请求超时时间通常设置较短,避免影响用户体验
- 内存泄漏:确保请求完成后,从跟踪列表中移除,避免内存泄漏
- 实时数据更新:结合定时刷新机制,确保大屏数据的实时性
九、总结:请求合并,让大屏飞起来!
通过实现请求合并,我们可以为大屏应用带来以下好处:
- 减少网络请求次数:将多个相同请求合并为一个,减少80%以上的网络请求
- 降低服务器压力:减少服务器需要处理的请求数量,提高服务器响应速度
- 提升大屏加载速度:减少等待网络请求的时间,让大屏更快呈现
- 确保数据一致性:所有组件使用相同的数据,避免数据不一致问题
- 优化用户体验:大屏加载更快,数据更同步,用户体验更好
请求合并是一种简单而强大的大屏性能优化技术,它可以在不改变现有代码结构的情况下,显著提升大屏应用的性能。无论是工业监控大屏还是城市管理大屏,请求合并都能带来明显的性能提升。
十、预告:下一篇博客内容
在这篇博客中,我们学习了大屏请求合并的实现方法。在下一篇博客中,我们将学习另一个专为大屏优化的重要技术——数据缓存,它可以:
- 缓存请求结果,避免短时间内的重复请求
- 支持自定义缓存时间,适配不同大屏数据的更新频率
- 结合请求合并,实现更强大的大屏性能优化
- 支持内存缓存和持久化缓存,满足不同大屏场景的需求
大屏性能优化没有终点,只有不断的探索和实践。让我们一起打造更快、更流畅的大屏应用!🚀