普通视图

发现新文章,点击刷新页面。
今天 — 2025年11月28日首页

【Vue3】大屏性能优化黑科技:Vue 3 中实现请求合并,让你的大屏飞起来!

作者 抱琴_
2025年11月27日 18:03

前言

作为大屏开发者,你是否遇到过这样的困扰:一个复杂的大屏页面,多个组件同时请求同一个接口,导致浏览器在短时间内发起了 N 次相同的网络请求?这不仅拖慢了大屏的加载速度,还可能导致数据不一致。今天,我将分享一个专为大屏优化的黑科技——请求合并,让你的 Vue 3 大屏应用瞬间提升一个档次!

一、大屏场景:为什么需要请求合并?

想象一下这个场景:

你正在开发一个 工业监控大屏 ,页面上有多个组件需要展示实时生产数据:

  • 顶部状态栏显示总生产量和合格率
  • 左侧设备列表显示所有设备的运行状态
  • 中间图表区域展示生产趋势图
  • 右侧告警面板显示当前告警数量 当大屏加载时,这四个组件都会发起 GET /api/production/stats 请求获取实时生产统计数据。如果没有请求合并,浏览器会在短时间内发起 4 次完全相同的网络请求!

在大屏应用中,这种情况会导致更严重的问题:

  1. 大屏加载缓慢 :多个请求同时发起,网络带宽被占用,导致大屏无法快速呈现
  2. 数据不同步 :不同组件接收到数据的时间不同,导致大屏数据不一致
  3. 服务器压力大 :大屏通常需要实时刷新,频繁的重复请求会给服务器带来巨大压力
  4. 影响实时性 :过多的网络请求会导致数据更新延迟,影响大屏的实时监控效果

二、解决方案:请求合并是什么?

请求合并 是一种专为大屏优化的性能技术,它的核心思想是:

当大屏上的 多个组件同时请求相同数据 时,只执行 一次实际的网络请求 ,然后将结果分发给所有等待的组件。

用一句话概括: 合并相同请求,共享实时数据 。

三、实现原理:如何为大屏实现请求合并?

大屏应用的请求合并实现需要考虑以下特点:

  1. 高频请求 :大屏通常需要频繁刷新数据
  2. 实时数据 :数据更新频率高,需要保证数据的时效性
  3. 多组件共享 :多个可视化组件需要相同的数据
  4. 性能敏感 :大屏对性能要求极高,需要快速响应

我们将使用 Map 来跟踪正在进行的请求,并使用 Promise 来实现结果的分发。

核心实现步骤:

  1. 生成请求唯一标识 :根据请求的 URL、方法、参数等生成唯一的请求键
  2. 检查请求状态 :检查是否有相同的请求正在进行
  3. 复用或发起请求 :如果有,直接复用;如果没有,发起新请求
  4. 分发请求结果 :将请求结果分发给所有等待的组件
  5. 清理请求跟踪 :请求完成后,从跟踪列表中移除

四、代码实现: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%

七、大屏应用场景

请求合并特别适合以下大屏场景:

  1. 工业监控大屏:多个组件同时请求设备状态、生产数据等
  2. 城市管理大屏:多个组件同时请求人口、交通、环境等数据
  3. 金融监控大屏:多个组件同时请求股票、汇率、交易数据等
  4. 物流监控大屏:多个组件同时请求货物、车辆、仓库数据等
  5. 能源监控大屏:多个组件同时请求电力、水资源、燃气数据等

八、大屏优化注意事项

  1. 请求标识的唯一性:确保生成的请求标识能准确区分不同的大屏请求
  2. 请求失败的处理:确保请求失败时,所有等待的组件都能得到正确的错误信息
  3. 请求超时的处理:大屏请求超时时间通常设置较短,避免影响用户体验
  4. 内存泄漏:确保请求完成后,从跟踪列表中移除,避免内存泄漏
  5. 实时数据更新:结合定时刷新机制,确保大屏数据的实时性

九、总结:请求合并,让大屏飞起来!

通过实现请求合并,我们可以为大屏应用带来以下好处:

  1. 减少网络请求次数:将多个相同请求合并为一个,减少80%以上的网络请求
  2. 降低服务器压力:减少服务器需要处理的请求数量,提高服务器响应速度
  3. 提升大屏加载速度:减少等待网络请求的时间,让大屏更快呈现
  4. 确保数据一致性:所有组件使用相同的数据,避免数据不一致问题
  5. 优化用户体验:大屏加载更快,数据更同步,用户体验更好

请求合并是一种简单而强大的大屏性能优化技术,它可以在不改变现有代码结构的情况下,显著提升大屏应用的性能。无论是工业监控大屏还是城市管理大屏,请求合并都能带来明显的性能提升。

十、预告:下一篇博客内容

在这篇博客中,我们学习了大屏请求合并的实现方法。在下一篇博客中,我们将学习另一个专为大屏优化的重要技术——数据缓存,它可以:

  • 缓存请求结果,避免短时间内的重复请求
  • 支持自定义缓存时间,适配不同大屏数据的更新频率
  • 结合请求合并,实现更强大的大屏性能优化
  • 支持内存缓存和持久化缓存,满足不同大屏场景的需求

大屏性能优化没有终点,只有不断的探索和实践。让我们一起打造更快、更流畅的大屏应用!🚀

❌
❌