你的位置:【欧冠体育正规平台】 > 人才招聘 > 十二个 Vue 开发中的性能优化小技巧
十二个 Vue 开发中的性能优化小技巧
发布日期:2022-08-07 19:26    点击次数:126

 性能优化,是每个开发者都市遇到的成就,特殊是今朝越来越珍视休会,以及竞争越来越猛烈的情形下,关于我们开发者来说,只实现迭代,把功用做好是远远不敷的,最首要的是把产品做好,让更多人违心应用,让用户用得更爽,这不也是我们开发者价钱与才能的发挥阐发吗。

珍视性能成就,优化产品的休会,比起改几个有关痛痒的 bug 要有价钱很多

本文记载了我在 Vue 名目日常开发中的一些小技巧,空话不多说,我们起头吧

一. 长列表性能优化 1. 不做照顾式

比喻会员列表、商品列表之类的,只是纯正的数据展现,不会有任何静态改变的场景下,就不须要对数据做照顾化处理惩罚,可以或许大大提升衬着速度

比喻应用 Object.freeze() 冻结一个工具,MDN的形貌是 该编制冻结的工具不克不迭被编削;即不克不迭向这个工具增加新属性,不克不迭删除已有属性,不克不迭编削该工具已有属性的可罗列性、可设置性、可写性,以及不克不迭编削已有属性的值,以及该工具的原型也不克不迭被编削 

export default {   data: () => ({     userList: []  }),   async created() {     const users = await axios.get("/api/users");     this.userList = Object.freeze(users);  }  }; 

Vue2 的照顾式源码地点:src/core/observer/index.js - 144行 是这样的 

export function defineReactive (...){     const property = Object.getOwnPropertyDescriptor(obj, key)     if (property && property.configurable === false) {         return    }     ... } 

可以或许看到一同头就鉴定 configurable 为 false 的间接前去不做照顾式处理惩罚

configurable 为 false 默示这个属性是不克不迭被编削的,而冻结的工具的 configurable 就是为 false

Vue3 里则是增加了照顾式flag,用于标记目的工具范例

2. 虚拟动弹

假定是大数据很长的列表,全副衬着的话一次性创立太多 DOM 就会极度卡,这时候就能用虚拟动弹,只衬着少部份(含可视地区)地区的内容,尔后动弹的时光,接续替代可视地区的内容,仿照出动弹的结果 

<recycle-scroller   class="items"   :items="items"   :item-size="24"  >   <template v-slot="{ item }">     <FetchItemView       :item="item"       @vote="voteItem(item)"     />   </template>  </recycle-scroller> 

参考 vue-virtual-scroller、vue-virtual-scroll-list

道理是监听动弹事宜,静态更新须要发挥阐发的 DOM,并计算出在视图中的位移,这也意味着在动弹进程须要实时计算,有必定成本,所以假定数据量不是很大的情形下,用通俗的动弹就行

二. v-for 遍历防止同时应用 v-if

为何要防止同时应用 v-for 和 v-if

在 Vue2 中 v-for 优先级更高,所以编译进程中会把列表元素全副遍历生成虚拟 DOM,再来经由过程 v-if 鉴定吻合条件的才衬着,就会构成性能的糟践,因为我们停留的是不吻合条件的虚拟 DOM都不要生成

在 Vue3 中 v-if 的优先级更高,就意味着当鉴定条件是 v-for 遍历的列表中的属性的话,v-if 是拿不到的

所以在一些须要同时用到的场景,就能经由过程计算属性来过滤一如下表,如下 

<template>     <ul>       <li v-for="item in activeList" :key="item.id">        {{ item.title }}       </li>     </ul>  </template>  <script>  // Vue2.x  export default {     computed: {       activeList() {         return this.list.filter( item => {           return item.isActive        })      }    }  }  // Vue3  import { computed } from "vue";  const activeList = computed(() => {   return list.filter( item => {     return item.isActive  })  })  </script
三. 列表应用仅有 key

好比有一个列表,我们须要在中央拔出一个元素,在不应用 key 或许应用 index 作为 key 会发生什么变换呢?先看个图

如图的 li1 和 li2 不会从头衬着,这个没有争议的。而 li三、li四、li5 都市从头衬着

因为在不应用 key 或许列表的 index 作为 key 的时光,每个元素对应的职位地方纠葛都是 index,上图中的终局间接导致我们拔出的元素到后面的全副元素,对应的职位地方纠葛都发生了厘革,所以在 patch 进程中会将它们全都执行更新操作,再从头衬着。

这可不是我们想要的,我们停留的是衬着增加的那一个元素,别的四个元素不做任何厘革,也就不要从头衬着

而在应用仅有 key  的情形下,每个元素对应的职位地方纠葛就是 key,来看一下应用仅有 key 值的情形下

这样如图中的 li3 和 li4 就不会从头衬着,因为元素内容没发生改变,对应的职位地方纠葛也没有发生改变。

这也是为何 v-for 必需要写 key,并且不倡导开发中应用数组的 index 作为 key 的启事

四. 应用 v-show 复用 DOM

v-show:是衬着组件,尔后改变组件的 display 为 block 或 none  v-if:是衬着或不衬着组件

所以关于可以或许频繁改变条件的场景,就应用 v-show 减省性能,特殊是 DOM 组织越宏壮收益越大

不过它也有优势,就是 v-show 在一同头的时光,全体门支外部的组件都市衬着,对应的生命周期钩子函数都市执行,而 v-if 只会加载鉴定条件射中的组件,所以须要痛处差别场景应用相宜的指令

比喻上面的用 v-show 复用DOM,比 v-if/v-else 结果好 

<template>   <div>     <div v-show="status" class="on">       <my-components />     </div>     <section v-show="!status" class="off">       <my-components >     </section>  </div>  </template

道理就是应用 v-if 今后提变换的时光,触发 diff 更新,缔造新旧 vnode 不一致,就会移除全副旧的 vnode,再从头创立新的 vnode,尔后创立新的 my-components 组件,又会阅历组件本身初始化,render,patch 等进程,而 v-show 在条件变换的时光,新旧 vnode 是分歧的,就不会执行移除创立等一系列流程

五. 有形态的组件用函数式组件

关于一些纯展现,没有照顾式数据,没有形态打点,也不消生命周期钩子函数的组件,我们就能设置成函数式组件,行进衬着性能,因为会把它当作一个函数来处理惩罚,所以开销很低

道理是在 patch 进程中关于函数式组件的 render 生成的虚拟 DOM,不会有递归子组件初始化的进程,所以衬着开销会低良多

它可以或许担任 props,然则因为不会创立实例,所之外部不克不迭应用 this.xx 取得组件属性,写法如下 

<template functional>   <div>     <div class="content">{{ value }}</div>   </div>  </template>  <script>  export default {   props: ['value']  }  </script>  // 或许  Vue.component('my-component', {   functional: true, // 默示该组件为函数式组件   props: { ... }, // 可选   // 第二个参数为凹凸文,没有 this   render: function (createElement, context) {     // ...  }  }) 
六. 子组件支解

先看个例子 

<template>   <div :style="{ opacity: number / 100 }">     <div>{{ someThing() }}</div>   </div>  </template>  <script>  export default {   props:['number'],人才招聘   methods: {     someThing () { /* 耗时使命 */ }  }  }  </script

上面这样的代码中,每次父组件传已往的 number 发生变换时,每次都市从头衬着,并且从头执行 someThing 这个耗时使命

所以优化的话一个是用计算属性,因为计算属性本身有缓存计算终局的特点

第二个是拆分成子组件,因为 Vue 的更新是组件粒度的,诚然第次数据变换都市导致父组件的从头衬着,然则子组件却不会从头衬着,因为它的外部没有任何变换,耗时使命自然也就不会从头执行,是以性能更好,优化代码如下 

<template>  <div>     <my-child />  </div>  </template>  <script>  export default {  components: {     MyChild: {      methods: {         someThing () { /* 耗时使命 */ }       },        render (h) {        return h('div', this.someThing())     }    }  }  }  </script
七. 变量外埠化

俭朴说就是把会屡次引用的变量生活生涯起来,因为每次拜访 this.xx 的时光,由是以照顾式工具,所以每次都市触发 getter,尔后执行寄托采集的相干代码,假定应用变量次数越多,性能自然就越差

从需要上说在一个函数里一个变量执行一次寄托采集就够了,但是良多人习性性的在名目中大量写 this.xx,而轻忽了 this.xx 迎面做的事,就会导致性能成就了

比喻上面例子 

<template>    <div :style="{ opacity: number / 100 }"> {{ result  }}</div>  </template>  <script>  import { someThing } from '@/utils'  export default {   props: ['number'],  computed: {      base () { return 100 },      result () {       let base = this.base, number = this.number //   生活生涯起来        for (let i = 0; i < 1000; i++) {        number += someThing(base) // 防止频繁引用  this.xx       }        return number     }  }  }  </script
八. 第三方插件按需引入

比喻 Element-UI 这样的第三方组件库可以或许按需引入防止体积太大,特殊是名目不大的情形下,更没有须要完备引入组件库 

// main.js import Element3 from "plugins/element3";  Vue.use(Element3)  // element3.js  // 完备引入  import element3 from "element3";  import "element3/lib/theme-chalk/index.css";   // 按需引入  // import "element3/lib/theme-chalk/button.css";  // ...  // import {   // ElButton,   // ElRow,   // ElCol,   // ElMain,   // .....  // } from "element3";  export default function (app) {   // 完备引入   app.use(element3)    // 按需引入   // app.use(ElButton);  } 
九. 路由懒加载

我们晓得 Vue 是单页应用,所以假定没有效懒加载,就会导致进入首页时须要加载的内容适量,时光太长,就会出现长时分的白屏,很倒运于用户休会,SEO 也不敌对

所以可以或许去用懒加载将页面举行分手,须要的时光才加载对应的页面,以分担首页的加载压力,削减首页加载时光

没有效路由懒加载: 

import Home from '@/components/Home'  const router = new VueRouter({     routes: [      { path: '/home', component: Home }  ] }) 

用了路由懒加载: 

const router = new VueRouter({  routes: [    { path: '/home', component: () =>   import('@/components/Home') },    { path: '/login', component:   require('@/components/Home').default } ]  }) 

在进入这个路由的时光才会走对应的 component,尔后运行 import 编译加载组件,可以或许理解为 Promise 的 resolve 机制

 import:Es6语规律范、编译时调用、是解构进程、不支持变量函数等  require:AMD尺度、运行时调用、是赋值进程,支持变量计算函数等

更多有关前端模块化的内容可以或许看我另外一篇文章 前端模块化尺度详细总结

十. keep-alive缓存页面

比喻在表单输入页面进入下一步后,再前去上一步到表单页时要留存表单输入的内容、比喻在列表页>概况页>列表页,这样来回跳转的场景等

我们均可以或许经由过程内置组件 <keep-alive></keep-alive> 来把组件缓存起来,在组件切换的时光不举行卸载,这样当再次前去的时光,就能从缓存中倏地衬着,而不是从头衬着,以减省性能

只有要包裹想要缓存的组件即可 

<template>   <div id="app">     <keep-alive>       <router-view/>     </keep-alive>  </div>  </template
 也可以用 include/exclude 来 缓存/不缓存 指定组件  可经由过程两个生命周期 activated/deactivated 来取得今后组件形态 十一. 事宜的烧毁

Vue 组件烧毁时,会自动解绑它的全副指令及事宜监听器,然则仅限于组件本身的事宜

而关于守时器、addEventListener 注册的监听器等,就须要在组件烧毁的生命周期钩子中手动烧毁或解绑,以防止内存泄露 

<script>  export default {     created() {       this.timer = setInterval(this.refresh, 2000)      addEventListener('touchmove',   this.touchmove, false)    },     beforeDestroy() {       clearInterval(this.timer)        this.timer = null        removeEventListener('touchmove',  this.touchmove, false)    } }  </script
十二. 图片懒加载

图片懒加载就是关于有良多图片的页面,为了行进页面加载速度,只加载可视地区内的图片,可视地区外的等到动弹到可视地区后再去加载

这个功用一些 UI 框架都有自带的,假定没有呢?

推选一个第三方插件 vue-lazyload 

npm i vue-lazyload -S  // main.js  import VueLazyload from 'vue-lazyload'  Vue.use(VueLazyload)  // 接着就能在页面中应用 v-lazy 懒加载图片了  <img v-lazy="/static/images/1.png"> 

或许本身造轮子,手动封装一个自定义指令,这里封装好了一个兼容各阅读器的版本的,主若是鉴定阅读器支不支持 IntersectionObserver API,支持就用它实现懒加载,不支持就用监听 scroll 事宜+俭省的编制实现 

const LazyLoad = {   // install编制   install(Vue, options) {    const defaultSrc = options.default    Vue.directive('lazy', {      bind(el, binding) {        LazyLoad.init(el, binding.value, defaultSrc)    },       inserted(el) {       if (IntersectionObserver) {          LazyLoad.observe(el)     } else {          LazyLoad.listenerScroll(el)        }    },  }) },   // 初始化   init(el, val, def) {  el.setAttribute('data-src', val)     el.setAttribute('src', def)  },   // 行使IntersectionObserver监听el  observe(el) {     var io = new IntersectionObserver((entries) => {      const realSrc = el.dataset.src       if (entries[0].isIntersecting) {     if (realSrc) {         el.src = realSrc           el.removeAttribute('data-src')        }      }   })    io.observe(el)  },   // 监听scroll事宜  listenerScroll(el) {     const handler =  LazyLoad.throttle(LazyLoad.load, 300)   LazyLoad.load(el)    window.addEventListener('scroll', () => {       handler(el)    })  },   // 加载其实图片   load(el) {     const windowHeight =  document.documentElement.clientHeight   const elelTop = el.getBoundingClientRect().top     const elelBtm =   el.getBoundingClientRect().bottom    const realSrc = el.dataset.src     if (elTop - windowHeight < 0 && elBtm > 0) {    if (realSrc) {        el.src = realSrc         el.removeAttribute('data-src')      }    }  },   // 俭省   throttle(fn, delay) {    let timer     let prevTime     return function (...args) {      const currTime = Date.now()     const context = this      if (!prevTime) prevTime = currTime      clearTimeout(timer)       if (currTime - prevTime > delay) {        prevTime = currTime           fn.apply(context, args)        clearTimeout(timer)         return      }     timer = setTimeout(function () {      prevTime = Date.now()      timer = null       fn.apply(context, args)    }, delay)   }  },  }  export default LazyLoad 

应用上是这样的,用 v-LazyLoad 接替 src 

<img v-LazyLoad="xxx.jpg" /> 
十三. SSR

这一点我在名目中也没有实际过,就不班门弄斧了