MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue网络请求 性能优化与请求合并的技巧分享

2023-07-095.0k 阅读

一、Vue 网络请求基础

在 Vue 项目开发中,网络请求是与后端服务器交互获取或提交数据的重要手段。通常我们会使用一些库来处理网络请求,比如 axios。Axios 是一个基于 Promise 的 HTTP 客户端,它在浏览器和 Node.js 中都能使用,而且提供了简洁易用的 API。

安装 Axios 很简单,通过 npm 或者 yarn 都可以进行安装:

npm install axios
# 或者
yarn add axios

在 Vue 项目中使用 Axios 发送一个简单的 GET 请求示例如下:

import axios from 'axios';

export default {
  methods: {
    async getData() {
      try {
        const response = await axios.get('/api/data');
        console.log(response.data);
      } catch (error) {
        console.error('请求出错:', error);
      }
    }
  }
};

上述代码中,axios.get('/api/data') 发起了一个 GET 请求到 /api/data 接口。如果请求成功,response.data 会包含服务器返回的数据。如果请求失败,catch 块会捕获到错误并在控制台打印错误信息。

POST 请求示例如下:

import axios from 'axios';

export default {
  methods: {
    async postData() {
      const data = {
        key1: 'value1',
        key2: 'value2'
      };
      try {
        const response = await axios.post('/api/data', data);
        console.log(response.data);
      } catch (error) {
        console.error('请求出错:', error);
      }
    }
  }
};

这里 axios.post('/api/data', data) 发送一个 POST 请求到 /api/data 接口,同时将 data 对象作为请求体发送给服务器。

二、Vue 网络请求性能优化

(一)合理设置请求超时时间

在网络请求过程中,可能会因为网络不稳定或者服务器响应过慢等原因导致请求长时间处于等待状态,这会严重影响用户体验。设置合理的请求超时时间可以避免这种情况。

Axios 中可以通过 timeout 配置项来设置请求超时时间,单位是毫秒。示例如下:

import axios from 'axios';

export default {
  methods: {
    async getDataWithTimeout() {
      try {
        const response = await axios.get('/api/data', {
          timeout: 5000 // 设置超时时间为 5 秒
        });
        console.log(response.data);
      } catch (error) {
        if (error.code === 'ECONNABORTED' && error.message.includes('timeout')) {
          console.error('请求超时');
        } else {
          console.error('请求出错:', error);
        }
      }
    }
  }
};

在上述代码中,如果请求在 5 秒内没有得到响应,就会触发超时错误。通过判断 error.code 是否为 ECONNABORTEDerror.message 中是否包含 timeout 来确定是超时错误还是其他错误。

(二)减少不必要的请求

  1. 数据缓存 在 Vue 中,可以使用浏览器的本地存储(localStorage)或者会话存储(sessionStorage)来缓存一些不经常变化的数据。例如,如果一个页面需要展示一些基本配置信息,这些信息在短时间内不会改变,那么可以在第一次请求获取到数据后,将其存储到本地存储中。后续再次需要这些数据时,先从本地存储中读取,如果本地存储中有数据且未过期,就不再发起网络请求。

示例代码如下:

import axios from 'axios';

export default {
  data() {
    return {
      configData: null
    };
  },
  methods: {
    async getConfigData() {
      const cachedData = localStorage.getItem('configData');
      if (cachedData) {
        this.configData = JSON.parse(cachedData);
        return;
      }
      try {
        const response = await axios.get('/api/config');
        this.configData = response.data;
        localStorage.setItem('configData', JSON.stringify(this.configData));
      } catch (error) {
        console.error('请求出错:', error);
      }
    }
  }
};
  1. 条件判断 在发送网络请求之前,根据业务逻辑进行条件判断,只有在必要的情况下才发起请求。比如,在一个搜索功能中,如果用户输入的关键词和上一次搜索的关键词相同,且搜索结果还在缓存中,那么就不需要再次发起搜索请求。
<template>
  <div>
    <input v-model="searchKeyword" @input="handleSearch">
    <ul>
      <li v-for="item in searchResults" :key="item.id">{{ item.name }}</li>
    </ul>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      searchKeyword: '',
      lastSearchKeyword: '',
      searchResults: [],
      cachedResults: {}
    };
  },
  methods: {
    async handleSearch() {
      if (this.searchKeyword === this.lastSearchKeyword && this.cachedResults[this.searchKeyword]) {
        this.searchResults = this.cachedResults[this.searchKeyword];
        return;
      }
      this.lastSearchKeyword = this.searchKeyword;
      try {
        const response = await axios.get(`/api/search?keyword=${this.searchKeyword}`);
        this.searchResults = response.data;
        this.cachedResults[this.searchKeyword] = this.searchResults;
      } catch (error) {
        console.error('请求出错:', error);
      }
    }
  }
};
</script>

(三)优化请求并发数量

在某些情况下,一个页面可能需要同时发起多个网络请求。然而,过多的并发请求会占用大量的网络资源,导致网络拥堵,反而降低了请求的效率。在 Vue 中,可以通过控制并发请求的数量来优化性能。

Axios 提供了 axios.allaxios.spread 方法来处理并发请求。axios.all 可以接收一个包含多个 Promise(通常是多个 Axios 请求)的数组,并返回一个新的 Promise,只有当数组中的所有 Promise 都 resolved 时,这个新的 Promise 才会 resolved。axios.spread 用于将 axios.all 返回的结果展开。

假设我们有两个请求,一个获取用户信息,一个获取用户订单列表:

import axios from 'axios';

export default {
  data() {
    return {
      userInfo: null,
      orderList: []
    };
  },
  methods: {
    async getUserAndOrder() {
      try {
        const [userResponse, orderResponse] = await axios.all([
          axios.get('/api/user'),
          axios.get('/api/orders')
        ]);
        this.userInfo = userResponse.data;
        this.orderList = orderResponse.data;
      } catch (error) {
        console.error('请求出错:', error);
      }
    }
  }
};

但是,如果有很多请求,同时发起可能会造成性能问题。这时可以通过队列的方式来控制并发数量。以下是一个简单的实现:

class RequestQueue {
  constructor(maxConcurrent) {
    this.maxConcurrent = maxConcurrent;
    this.queue = [];
    this.activeCount = 0;
  }

  addRequest(request) {
    return new Promise((resolve, reject) => {
      this.queue.push({ request, resolve, reject });
      this.processQueue();
    });
  }

  processQueue() {
    while (this.activeCount < this.maxConcurrent && this.queue.length > 0) {
      const { request, resolve, reject } = this.queue.shift();
      this.activeCount++;
      request()
      .then(response => {
          resolve(response);
        })
      .catch(error => {
          reject(error);
        })
      .finally(() => {
          this.activeCount--;
          this.processQueue();
        });
    }
  }
}

// 使用示例
const queue = new RequestQueue(2); // 最大并发数为 2
const request1 = () => axios.get('/api/data1');
const request2 = () => axios.get('/api/data2');
const request3 = () => axios.get('/api/data3');

queue.addRequest(request1)
 .then(response1 => {
      console.log('Request 1 response:', response1.data);
    })
queue.addRequest(request2)
 .then(response2 => {
      console.log('Request 2 response:', response2.data);
    })
queue.addRequest(request3)
 .then(response3 => {
      console.log('Request 3 response:', response3.data);
    })

(四)优化请求头和响应头

  1. 请求头 在请求头中设置合适的字段可以提高请求的效率。例如,设置 Content-Type 字段来告知服务器请求体的数据格式。对于 JSON 格式的数据,通常设置 Content-Type: application/json

Axios 中设置请求头示例如下:

import axios from 'axios';

export default {
  methods: {
    async postJsonData() {
      const data = {
        key1: 'value1',
        key2: 'value2'
      };
      try {
        const response = await axios.post('/api/data', data, {
          headers: {
            'Content-Type': 'application/json'
          }
        });
        console.log(response.data);
      } catch (error) {
        console.error('请求出错:', error);
      }
    }
  }
};

另外,如果服务器支持,还可以设置 Accept 头来告知服务器客户端期望接收的数据格式,比如 Accept: application/json 表示期望接收 JSON 格式的数据。

  1. 响应头 关注响应头中的信息也有助于优化性能。例如,Cache-Control 头可以控制响应的缓存策略。如果服务器设置了合适的 Cache-Control 头,浏览器可以根据其规则对响应进行缓存,从而减少后续相同请求的网络开销。

在 Vue 中,可以通过 Axios 的拦截器来处理响应头。例如,打印响应头中的 Cache-Control 信息:

import axios from 'axios';

axios.interceptors.response.use(response => {
  console.log('Cache-Control:', response.headers['cache-control']);
  return response;
}, error => {
  return Promise.reject(error);
});

三、Vue 网络请求合并技巧

(一)基于用户操作的请求合并

  1. 防抖与节流 在用户进行一些频繁操作,如输入搜索关键词、点击按钮等时,可能会触发多次网络请求。使用防抖(Debounce)和节流(Throttle)技术可以有效减少不必要的请求。

防抖是指在一定时间内,如果再次触发相同事件,会取消之前的定时器重新计时,只有在指定时间内没有再次触发事件,才会执行回调函数。例如,在搜索框输入时,用户可能会快速输入多个字符,如果每次输入都发起搜索请求,会造成大量不必要的请求。使用防抖可以在用户停止输入一段时间后再发起请求。

在 Vue 中可以通过自定义指令实现防抖,示例代码如下:

<template>
  <div>
    <input v-debounce:500="handleSearch" v-model="searchKeyword">
  </div>
</template>

<script>
export default {
  data() {
    return {
      searchKeyword: ''
    };
  },
  methods: {
    handleSearch() {
      console.log('发起搜索请求:', this.searchKeyword);
      // 这里可以发起实际的搜索请求
    }
  },
  directives: {
    debounce: {
      inserted(el, binding) {
        let timer;
        el.addEventListener('input', () => {
          clearTimeout(timer);
          timer = setTimeout(() => {
            binding.value();
          }, binding.arg);
        });
      }
    }
  }
};
</script>

节流是指在一定时间内,无论触发多少次事件,都只会执行一次回调函数。例如,在一个频繁点击的按钮上应用节流,防止短时间内多次点击触发多次相同的网络请求。

同样通过自定义指令实现节流:

<template>
  <div>
    <button v-throttle:2000="handleClick">点击</button>
  </div>
</template>

<script>
export default {
  methods: {
    handleClick() {
      console.log('按钮点击,发起请求');
      // 这里可以发起实际的请求
    }
  },
  directives: {
    throttle: {
      inserted(el, binding) {
        let lastTime = 0;
        el.addEventListener('click', () => {
          const now = new Date().getTime();
          if (now - lastTime >= binding.arg) {
            binding.value();
            lastTime = now;
          }
        });
      }
    }
  }
};
</script>
  1. 批量提交 在一些表单操作中,用户可能会多次修改表单数据。如果每次修改都立即提交数据到服务器,会产生大量的网络请求。可以采用批量提交的方式,将用户的多次操作合并为一次请求。

例如,在一个商品编辑页面,用户可以修改商品的名称、价格、描述等多个字段。可以在用户点击保存按钮时,将所有修改的数据一起提交到服务器。

<template>
  <div>
    <input v-model="product.name" placeholder="商品名称">
    <input v-model.number="product.price" placeholder="商品价格">
    <textarea v-model="product.description" placeholder="商品描述"></textarea>
    <button @click="saveProduct">保存</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      product: {
        name: '',
        price: 0,
        description: ''
      }
    };
  },
  methods: {
    async saveProduct() {
      try {
        const response = await axios.post('/api/products', this.product);
        console.log('保存成功:', response.data);
      } catch (error) {
        console.error('保存出错:', error);
      }
    }
  }
};
</script>

(二)基于业务逻辑的请求合并

  1. 相同接口不同参数的合并 有时候,可能会对同一个接口发起多个请求,只是请求参数不同。例如,在一个电商平台中,需要获取不同分类的商品列表,虽然接口都是 /api/products,但参数 category 不同。可以将这些请求合并为一个请求,服务器端根据参数返回相应的数据。

假设我们有一个函数 getProductsByCategory 用于获取不同分类的商品列表:

import axios from 'axios';

export default {
  methods: {
    async getProductsByCategory(categories) {
      const params = {
        categories: categories.join(',')
      };
      try {
        const response = await axios.get('/api/products', { params });
        console.log('获取商品列表成功:', response.data);
        // 处理返回的数据
      } catch (error) {
        console.error('获取商品列表出错:', error);
      }
    }
  }
};

在调用时,可以将多个分类作为参数传入:

// 获取电子产品和服装类商品列表
this.getProductsByCategory(['electronics', 'clothing']);
  1. 关联数据请求合并 在业务中,有些数据之间存在关联关系,需要分别请求不同的接口获取这些数据。例如,在一个博客系统中,需要获取文章列表,同时对于每篇文章,还需要获取作者信息。可以将获取文章列表和获取作者信息的请求合并为一个请求,减少网络开销。

假设文章接口为 /api/articles,作者接口为 /api/authors/:id。可以通过一次请求获取文章列表,同时在文章数据中包含作者信息。

服务器端可以通过关联查询数据库来实现这一点,而在前端,我们只需要发起一个请求:

import axios from 'axios';

export default {
  data() {
    return {
      articles: []
    };
  },
  methods: {
    async getArticlesWithAuthors() {
      try {
        const response = await axios.get('/api/articles?withAuthors=true');
        this.articles = response.data;
        console.log('获取文章及作者信息成功:', this.articles);
      } catch (error) {
        console.error('获取文章及作者信息出错:', error);
      }
    }
  }
};

在上述代码中,/api/articles?withAuthors=true 表示请求文章列表并同时获取作者信息,服务器端根据这个参数进行相应的处理并返回包含作者信息的文章列表数据。

(三)使用请求合并库

在 Vue 项目中,也可以使用一些专门的请求合并库来简化请求合并的操作。例如,vue-axios-multi 库可以方便地将多个 Axios 请求合并为一个请求。

首先安装该库:

npm install vue-axios-multi
# 或者
yarn add vue-axios-multi

然后在 Vue 项目中使用:

import Vue from 'vue';
import VueAxiosMulti from 'vue-axios-multi';
import axios from 'axios';

Vue.use(VueAxiosMulti, { axios });

export default {
  methods: {
    async combinedRequests() {
      const requests = [
        { url: '/api/user', method: 'get' },
        { url: '/api/orders', method: 'get' }
      ];
      try {
        const responses = await this.$axiosMulti(requests);
        const userResponse = responses[0];
        const orderResponse = responses[1];
        console.log('用户信息:', userResponse.data);
        console.log('订单列表:', orderResponse.data);
      } catch (error) {
        console.error('请求出错:', error);
      }
    }
  }
};

在上述代码中,this.$axiosMulti(requests) 方法接收一个包含多个请求配置的数组,并返回一个 Promise,当所有请求都完成时,这个 Promise 会 resolved,返回的结果是一个包含每个请求响应的数组。这样就方便地实现了多个请求的合并处理。

通过以上介绍的 Vue 网络请求性能优化与请求合并技巧,可以显著提升 Vue 应用在网络请求方面的性能,提高用户体验,同时降低服务器的负载压力,使应用更加高效稳定地运行。在实际项目中,需要根据具体的业务场景和需求,灵活选择和应用这些技巧。