【开发小记】vue项目优化

今天从多个方面来记录一下自己项目中考虑过的优化方案

一、代码层面

1、代码细节优化
首先从基本的代码层面来看,举个例子,比如我拿到了一个数组,需要对这个数组的内容做一些处理(比如需要对数组的内容遍历然后做一些处理等等),但是在当数组是空的时候就没有必要走下面的逻辑了。所以可以加一个判断语句,如果数组为空就不再进行后续的逻辑,这也可以减少很多代码的执行,从而提高性能。类似的还有很多,应该说是代码细节层面的一些优化了。
2、scoped时修改第三方组件样式
vue文件正常样式写在中,会被自动加上一个[data-v-xxxx]属性,但是第三方组件内部标签没有编译为这个属性,所以不能修改这个第三方组件。
为了父组件不影响子组件,用scoped,有一个方法是给第三方组件写class,然后在公共css或者当前页面写一个没有scoped的style,直接在里面修改第三方组件的样式,但是存在全局污染和命名冲突,约定的那个特殊的命名方式可以避免命名冲突。查找资料发现可以用深度选择器解决。深度选择器用/deep/,用到ElemntUI,又有预处理器就可以用.::v-deep。vue中过多使用scoped导致页面打包文件体积增大。通常能写在index中的样式尽量写在index中,我们可以通过在index样式中通过外层组件添加唯一class来区分组件+第三方样式来实现了类似于scoped的效果,又方便修改各种第三方组件的样式。

二、网络层面的优化

1、重复请求的取消:第一次请求还没回来之前就进行第二次同样的请求,这时候就应该把第二次请求给取消掉,防止重复请求
这里我们对axios请求进行了封装,采用了拦截器中进行了相应的处理。
在请求拦截器和响应拦截器之前,先实现了三个方法。
因为当请求方式,请求 URL和请求携带参数都一样时,就可以认为是相同的请求。因此在每次发起请求时,可以根据当前请求的请求方式、请求 URL地址和请求携带参数来生成一个唯一的 key;同时为每个请求创建一个专属的 CancelToken,然后将 key 和 Cancel 函数以键值对的形式保存到 Map对象中,使用 Map对象的好处是可以快速判断是否有重复的请求

// 用于根据当前请求的信息生成请求的key
  generateReqKey(config){
    const {method,url,params,data} = config;
    return [method,url,qs.stringify(params),qs.stringify(data)].join('&')
  }
  // 用于把当前请求信息添加到pendingRequest对象中去
  PendingRequest = new Map();
  addPendingRequest(config){
    const requestKey = this.generateReqKey(config);
    config.cancalToken = config.cancalToken || new axios.CancelToken((cancel)=>{
      if(!this.PendingRequest.has(requestKey)){
        this.PendingRequest.set(requestKey,cancel)
      }
    })
  }
  // 检查是否存在重复请求,若存在则需要取消已经发出的请求
  removeRequest(config){
    const requestKey = this.generateReqKey(config);
    if(this.PendingRequest.has(requestKey)){
      const CancelToken = this.PendingRequest.get(requestKey);
      CancelToken(requestKey);
      this.PendingRequest.delete(requestKey);
    }
  }

然后是配置拦截器
因为需要对所有的请求都进行处理,因此考虑使用 axios的拦截器机制来实现取消重复请求的功能。
axios的制作者为开发人员提供了请求拦截器和响应拦截器,各自的作用如下:
(1)请求拦截器:该类拦截器的作用是在请求发送前统一执行某些操作,比如在请求头中添加 token字段;
(2)响应拦截器:该类拦截器的作用是在接收到服务器响应后统一执行某些操作,比如根据不同的状态码来执行相对应的操作。

  interceptors(install) {
    install.interceptors.request.use(config => {
      // 检查是否存在重复请求
      this.removeRequest(config);
      // 把当前请求信息添加到pendingRequest对象中
      this.addPendingRequest(config);
      const token = localStorage.getItem('token');
      if (token) {
        config.headers.authorization = `token ${token}`;
      }

      return config
    }, err => {
      return Promise.reject(err)
    });

    install.interceptors.response.use(res => {
      // 从pendingRequest对象中移除请求
      this.removeRequest(res.config);
      const { data } = res;
      return data
    }, err => {
      // 从pendingRequest对象中移除请求
      this.removeRequest(err.config)
      return Promise.reject(err)
    });
  }

完整的网络请求封装代码如下:

import axios from 'axios';
// 引入qs库奖url中的参数转化为对象
import qs from "qs"

class HttpRequest {
  constructor(options) {
    this.defaults = {
      baseUrl: ''
    }

    this.defaults = { ...this.defaults, ...options }
  }
  // 用于根据当前请求的信息生成请求的key
  generateReqKey(config){
    const {method,url,params,data} = config;
    return [method,url,qs.stringify(params),qs.stringify(data)].join('&')
  }
  // 用于把当前请求信息添加到pendingRequest对象中去
  PendingRequest = new Map();
  addPendingRequest(config){
    const requestKey = this.generateReqKey(config);
    config.cancalToken = config.cancalToken || new axios.CancelToken((cancel)=>{
      if(!this.PendingRequest.has(requestKey)){
        this.PendingRequest.set(requestKey,cancel)
      }
    })
  }
  // 检查是否存在重复请求,若存在则需要取消已经发出的请求
  removeRequest(config){
    const requestKey = this.generateReqKey(config);
    if(this.PendingRequest.has(requestKey)){
      const CancelToken = this.PendingRequest.get(requestKey);
      CancelToken(requestKey);
      this.PendingRequest.delete(requestKey);
    }
  }
  interceptors(install) {
    install.interceptors.request.use(config => {
      // 检查是否存在重复请求
      this.removeRequest(config);
      // 把当前请求信息添加到pendingRequest对象中
      this.addPendingRequest(config);
      const token = localStorage.getItem('token');
      if (token) {
        config.headers.authorization = `token ${token}`;
      }

      return config
    }, err => {
      return Promise.reject(err)
    });

    install.interceptors.response.use(res => {
      // 从pendingRequest对象中移除请求
      this.removeRequest(res.config);
      const { data } = res;
      return data
    }, err => {
      // 从pendingRequest对象中移除请求
      this.removeRequest(err.config)
      return Promise.reject(err)
    });
  }

  request(options) {
    options = { ...this.defaults, ...options }
    const instance = axios.create(options);
    this.interceptors(instance);
    return instance
  }
}

2、限制失败请求重试的次数,避免无效请求过多,导致大量的资源消耗
3、善用缓存,减小网络请求的次数比如在我们的项目中,个人空间展示页,如果判断是自己访问自己的空间,那么就不用再去请求数据了,直接从vuex中或者localstorage中去取就可以了。
4、断网处理
在vuex中存放network的状态,根据network状态判断是否需要加载断网组件,断网情况就加载断网组件,不加载对应页面组件,点击刷新,就跳转refresh页面然后立刻返回来实现重新获取数据,所以新建refresh.vue,在beforeRouteEnter钩子种返回当前页面。

<template>  
    <div id="app">    
        <div v-if="!network">      
            <h3>我没网了</h3>      
            <div @click="onRefresh">刷新</div>      
        </div>    
        <router-view/>      
    </div>
</template>

<script>
    import { mapState } from 'vuex';
    export default {  
        name: 'App',  
        computed: {    
            ...mapState(['network'])  
        },  
        methods: {    
            // 通过跳转一个空页面再返回的方式来实现刷新当前页面数据的目的
            onRefresh () {      
                this.$router.replace('/refresh')    
            }  
        }
    }
</script>
// refresh.vue
beforeRouteEnter (to, from, next) {
    next(vm => {            
        vm.$router.replace(from.fullPath)        
    })    
}

三、业务层面的优化

1、防抖节流
防抖节流是优化高频率执行代码的一种手段,比如浏览器的resize、scroll、keypress、mousemove等事件在触发的时候,会不断的调用绑定的回调函数,极大的浪费资源,甚至降低前端的性能。
为了优化体验,我们可以采取防抖和节流的手段来减少调用的频率。
节流:n秒内只运行一次,若n秒内重复触发,则只有一次生效。
防抖:n秒后执行该事件,若在n秒内重复触发,则重新计时。

代码实现如下:
节流函数

// 节流函数
function throttled(fn,delay) {
  let timer = null;
  let startTime = Date.now();
  return function(){
    let curTime = Date.now();
    let remaining = delay-(curTime-startTime);
    let context = this;
    let args = arguments;
    clearTimeout(timer);
    if(remaining<=0){
      fn.apply(context,args);
      startTime = Date.now();
    }else{
      timer = setTimeout(fn,remaining);
    }
  }
}

防抖函数

// 防抖
function debounce(fn,wait,immediate){
  let timeout;
  return function(){
    let context = this;
    let args = arguments;
    if(timeout) clearTimeout(timeout);
    if(immediate){
      let callNow = !timeout; //第一次回立即执行,以后只有事件执行之后才会继续触发
      timeout = setTimeout(function(){
        timeout = null;
      },wait)
      if(callNow){
        fn.apply(context,args)
      }
    }else{
      timeout = setTimeout(function(){
        fn.apply(context,args);
      },wait);
    }
  }
}

二者的应用场景举例:
防抖是连续的事件,只需要触发一次回调:
(1)搜索框搜索输入,只需要用户最后一次输入完再发送请求
(2)手机号、邮箱验证输入检测
(3)窗口大小resize,只需要窗口调整完成后,计算窗口大小,防止重复渲染
节流是每间隔一段时间执行一次回调:
(1)滚动加载,加载更多或者滚动到底部监听
(2)搜索框,搜索联想功能

2.事件解绑
组件销毁之前解绑事件,避免内存占用过多

3.长列表优化
我们的项目中采用了分页的思想来分批请求数据。其余的还可以采用懒加载、虚拟滚动等。

4.首屏时间优化
可以采用预渲染技术。
可以用lighthouse去测检测一下性能
在这里插入图片描述

5.路由懒加载
需要的时候进行加载,把不同路由对应的组件分成不同代码块,路由被访问才加载对应组件。路由会定义很多页面,页面打包后放到单独的js文件会导致非常大,懒加载把页面进行划分,需要时才加载,减少首页加载速度,懒加载主要就是把对应组件打包成js代码块进入首屏不用加载过度的资源,从而减少首屏加载速度。

// 引入路由组件
import Welcome from '@/pages/Welcome'
import Home from '@/pages/Home'; //引入组件会打包在文件中,如果都用import的话会导致所有组件打包在一个文件中,导致文件很大
// 按需加载,访问路径的时候才会加载;不访问不加载
const Search = ()=> import('@/pages/Search');
const Community = ()=> import('@/pages/Community');
const MyRoom = ()=> import('@/pages/MyRoom');

** 6.组件懒加载**
用到了再加载,缩短加载时间

四、打包方面

(1)减少打包的体积:压缩代码、资源的体积
(2)缩短打包时长:cache-loader、thread-loader
cache-loader: 允许缓存以下 loaders 到(默认)磁盘或数据库。在一些性能开销较大的 loader 之前添加 cache-loader,以便将结果缓存到磁盘里。保存和读取这些缓存文件会有一些时间开销,所以请只对性能开销较大的 loader 使用此 loader。

thread-loader:
使用时,需将此 loader 放置在其他 loader 之前。放置在此 loader 之后的 loader 会在一个独立的 worker 池中运行。
在 worker 池中运行的 loader 是受到限制的。例如:
这些 loader 不能生成新的文件。
这些 loader 不能使用自定义的 loader API(也就是说,不能通过插件来自定义)。
这些 loader 无法获取 webpack 的配置。
每个 worker 都是一个独立的 node.js 进程,其开销大约为 600ms 左右。同时会限制跨进程的数据交换。
请仅在耗时的操作中使用此 loader!
(3)使用CDN:请求资源更快

参考文章:https://juejin.cn/post/7074573053979525151

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值