随着互联网的发展,越来越多的公司使用Vue。但是,随着项目的增长,难免会带来一系列的性能问题。笔者也为这些问题感到头疼,也研究了Vue的性能优化,从而避免了性能问题和不必要的返工。
为了以后能快速找到相关的学习内容,在这里做个记录,方便以后查看。同时,我也想总结一下这些内容,希望能帮助更多的小伙伴一起学习,一起成长。打架~
这个时候,很多小伙伴可能会说,“现在Vue3.0都快发布了,为什么还要优化2.0项目呢?”?因为公司80%的项目都是Vue2.0项目,迁移成本太高,只能优化调整性能。我就不细说了。我们开始吧。
使用异步组件。
打包时,Vue-cli会将所有相关文件打包成一个大的js文件。当用户浏览网页时,需要拉取整个js文件,这会导致页面初始化时出现长时间的白屏。这个问题真的很棘手。
想象一下,如果页面上有很多功能点,每个功能点对应一个不同的功能弹出窗口或表单。首先,页面上有很多功能。我们不知道用户想使用哪个功能,需要弹出哪个弹出窗口或表单。在异步组件的情况下,Vue-cli将在打包异步组件时将其单独打包到一个文件中。只有当用户使用它们的时候才会加载js文件,所以第一屏的渲染会起到一定的优化作用。
看看官网对异步组件的描述:
在大型应用程序中,我们可能需要将应用程序分成更小的代码块,并且只在必要时从服务器加载一个模块。为了简化,Vue允许您以工厂函数的形式定义组件,该函数异步解析组件定义。Vue仅在需要渲染组件时触发工厂函数,并缓存结果以备将来重新渲染。
//从Vue官网截取的代码。
vue.component(' async-web pack-example ',
()=import('。/my-async-component ')
)
为了解决加载组件时等待时间过长的问题,Vue官方提供了用于加载异步组件的异步组件:
//从Vue官网截取的代码。
const AsyncComponent=()=({ 0
//要加载的组件(应该是Promise对象)。
component: import('。/MyComponent . vue’),
//加载异步组件时使用的组件。
加载:加载组件,
//加载失败时使用的组件。
错误:错误组件,
//显示加载过程中组件的延迟时间。默认值为200(毫秒)。
delay: 200,
//如果提供了超时,并且组件加载已超时,
//使用加载失败时使用的组件。默认值为:无穷大。
超时: 3000
})
使用异步组件时,应注意以下几点:
如果只是需要异步加载一些组件,先加载一些组件,再加载一些组件,那么可以直接使用Vue官网的编写风格,直接使用setTimeOut。
如果你点击加载,你必须写v-if,否则你会报告一个错误,说测试组件没有注册。V-if是惰性的,只有当第一个值为真时,初始化才会开始。
初始化DOM缩减渲染。
还是如上所述,页面中有很多功能点,但是有很多弹出窗口等等。事实上,在这些弹出窗口开始时,所有弹出窗口都被认为只使用一个弹出窗口。为了节省页面初始化的渲染,在实际开发过程中,虽然解决了一些问题,但似乎开发过程并没有那么乐观,弹出的窗口里面出现了大量的v-if和v-show,维护起来太难了。
我有想过用组件/组件,但可想而知,一个组件/上的压力不是一点点。但这个问题依然存在,需要解决。在没有解决方案的情况下,使用两个标志来控制弹出窗口的显示和隐藏。
模板
差异
el-dialog title='提示'
v-if='isRenderDialog '
:可见。sync=' IsShowDialog '/El-dialog
"渲染对话框"按钮
/div
/模板
脚本
导出默认值{
数据:()=({ 0
isRenderDialog:false,
isShowDialo
g:false }), methods: { onShowDialog(){ !this.isRenderDialog && (this.isRenderDialog = true); this.$nextTick(() => { this.isShowDialog = true; }) } } } </script>上述代码中使用两个flag值控制Dialog一个是控制Dialog的渲染,一个控制Dialog的显示,当用户首次进入页面的时候则dialog元素不会被渲染,当用户点击按钮,对应的Dialog才会被渲染出来,当Dialog的DOM渲染完成使用在使用显示Dialog。
注:在$nextTick中显示dialog是为了保证dialog的动画效果,如果不使用$nextTick则dialog就会很生硬的出现。
组件内部请求数据
大家在做业务的时候,可能会有这种情况,当点击按钮之后,需要获取到该条数据的详情渲染到弹窗或者侧滑中,这种情况一定不在少数啦。笔者在开始做这个的时候就是,在点击的时候直接去获取点击的元素的详情数据,当数据返回之后把数据放到data中缓存,之后再传到组件中。
这样做不是不可行的,也是可以的,这样就会面临一个问题,第一点就是当弹窗中的渲染的元素过多的情况下,侧滑或者弹窗的动画效果会很卡,有的时候甚至是不动,瞬间就消失了。
最后经过反复的实验,把数据放到弹窗内部组件中去请求,保证弹窗或者侧滑出现的时候内置元素较少,当数据没有请求回来之前需要把弹框组件内的所有元素隐藏,使用loading代替,当弹窗或者侧滑关闭的使用需要把显示的组件销毁掉,保证里面的数据所占用的内存被释放,这样对于整体优化还是有一些帮助的。
tamplate少计算
由于业务情况的复杂程度,难免会某一个地方添加各种条件的渲染,例如:v-if=”isHide && selectList.length && (isA || isB)”,这里也只是举一个简单的例子可能在实际的开发过程中的情况远比这个要复杂的多,这种表达式看上去虽然说是可以维护的,但是长此以往下去就会暴露问题,这样做是很不利于维护的。
对于这种情况可以适当的使用methods或computed封装成方法,其实这样做的好处是方柏霓我们判断相同的表达式,如果其他的元素也有类似的需求可以直接使用这个方法。
v-for && v-bind:key
在使用v-for循环过程中,使用:key=”item.id”这样的代码对于代码的性能是很不友好的,因为当data数据更新的时候,新的状态值会和旧的状态值做对比,Vue在多diff算法的时候能够更快的定位到虚拟DOM的元素上。
其实说到这里就需要说明一下key在vue中到底起到一个什么样的作用,key属性其实是vue的一个优化,上文也说了就是为了更精准高效的定位到虚拟DOM,相当于使用key给数组中某个预算绑定到了一起,如果那个key对应的数据发生了变化,直接更新对应的DOM就可以了。
对于简短的for来说可以直接使用index作为key但是,如果大型列表的话最好还是不要使用index作为key了。举个例子,例如数组删除了一个元素,那么这个元素后方元素的下标全都前移了一位,之前key对应的数据和dom就会乱了,除非重新匹配key,那就容易产生错误。如果重新匹配key,等于全部重新渲染一遍,违背了使用key来优化更新dom的初衷。但是如果对于Vue玩的很透的同学来说可以可以忽略这一条。
Object.freeze
如果对Vue有一定了解的小伙伴都知道Vue是通过Object.defineProperty对数据进行挟持,来最终实现视图响应数据的变化,但是在实际的开发过程中,页面中有一部分可能不需要进行双向绑定,只是做单纯的渲染,数据一旦绑定之后不需要再做出任何改变的时候可以使用Object.freeze对数据做解绑。
先介绍以下Object.freeze内置函数,用于对接对象,冻结后的对象不会再被修改,不能对这个对象进行添加新属性, 不能删除已有属性,不能修改该对象已有属性的可枚举性,可配置性,可写性.此外冻结一个对象后该对象的原型也不能进行修改。
当数据量大的时候,这能够很明显的减少组件初始化的时间,这里有一个需要注意的点就是一旦被冻结的对象就再也不能被修改了。但是这里有一个问题需要注意的是,嗒嗒嗒,敲黑板!敲黑板!敲黑板!
虽然Object.freeze在一定程度上能够帮助我们提升一部分的数据性能,但是在使用的时候仍然需要谨慎使用。避免造成数据无法响应的问题。如果使用Object.freeze这个属性再次给其对象属性赋值时,则会抛出错误不能分配给对象的只读属性*。
用这种方法去提升性能如果数据量小的情况是无法感觉出来的。只有数据量大的时候,才会感觉到数据的明显变化。
渲染前处理
在渲染数据的时候,后端所返回的数据和UI设计图中所需要的数据格式不一致,比如:列表中需要展示一个时间,但是后端返回的是一个时间戳,那么前端就需要对这部分数据进行处理。一般来说处理这种情况有一些办法,使用函数,使用filter,还有就是在渲染之前把数据处理好。
笔者这里比较建议在渲染之前把所有的数据处理好,为什么?数据渲染之后完成之后才会去执行里面的函数或者是过滤器,这样会给页面渲染造成很明显的额外的负担。如果对Vue3.0了解的同学可以知道,在Vue3.0中已经把filter这个功能已经去掉了,推荐使用computed来实现相同相同的效果。
猜测内容:可能尤大大也发现了filter给页面渲染带来的额外的负担,并没有对页面的性能提升起到很大的作用。
functional
不是很多函数组件都需要方法,Vue中为了表示一个模板应该被编译成一个功能组件,在模板中 添加了functional属性。如果项目中的所使用的组件不是有状态的组件,那么就可以使用functional属性把这个组件转换成功能组件。
功能组件(不要与Vue的render函数混淆)是一个不包含状态和实例的组件。功能组件是一个没有状态或实例的组件。由于功能组件没有状态,因为不需要为Vue的数据响应之类的东西做初始化动作。功能组件仍然会像出入的props一样对数据更新做出响应,但是功能组件的自身,由于它不维护自己的状态,同时也因此无法知道自己的数据是否已经发生了改变。在大型项目中使用功能组件以后,在对于DOM渲染有重大的改进。
由于功能组件没有状态,因此不需要为Vue的反应系统之类的东西进行额外的初始化。功能组件仍然会像传入的新道具那样对更改做出反应,但是在组件本身内,由于它不维护自己的状态,因此无法知道何时数据已更改。
在许多情况下,功能组件可能不合适。毕竟,使用JavaScript框架的目的是构建更具反应性的应用程序。在Vue中,如果没有适当的反应系统,则无法执行此操作。
假设我们的组件接受一个prop.user,该对象是带有firstName和的对象lastName,并且我们想要呈现一个显示用户全名的模板。在功能<template>组件中,我们可以通过在组件定义上提供一个方法,然后使用$optionsVue提供的属性来访问我们的特殊方法来做到这一点:
<template functional>
<div>{{ $options.userFullName(props.user) }}</div>
</template>
<script>
export default {
props: {
user: Object
},
userFullName(user) {
return `${user.firstName} ${user.lastName}`
}
}
</script>
子组件中处理业务
页面中也会有很多的列表,列表中也会有各种各样的复杂的情况,这个时候可以把一些比较繁重的业务处理存放到其子组件中。
代码对比:
<template>
<div :style="{ opacity: number / 300 }">
<div>{{ heavy() }}</div>
</div>
</template>
<script>
export default {
props: ['number'],
methods: {
heavy () {
const n = 100000
let result = 0
for (let i = 0; i < n; i++) {
result += Math.sqrt(Math.cos(Math.sin(42)))
}
return result
},
},
}
</script>
优化后:
<template>
<div :style="{ opacity: number / 300 }">
<ChildComp/>
</div>
</template>
<script>
export default {
props: ['number'],
components: {
ChildComp: {
methods: {
heavy () { /* 长任务在子组件里。 */ }
},
render (h) {
return h('div', this.heavy())
}
}
}
}
</script>
当组件随着props:number的变化,组件patch重新渲染的时候,heavy长任务也会重新执行。但是如果能将没有与父组件相互依赖的元素,拆成一个组件,在父组件需要重新渲染的时候,因为与父组件没有依赖子组件并不会跟着重新渲染,响应的性能也能得到提升。
局部作用域
开发过程中会经常使用到一些计算属性或者Util函数,如果我们在循环过程中,不断的使用this.***去调用一个计算属性的时候,每次调用这个值计算属性都会计算一次,然而这个值却是一个固定不变的值,就造成了很大的性能的浪费。
如果当我们使用这些属性的时候,最好的方式是把对应的值取出来,然后再去使用。
<template>
<div :style="{ opacity: start / 300 }">{{ result }}</div>
</template>
<script>
export default {
props: ['start'],
computed: {
base () {
return 42
},
result ({ base, start }) {
let result = start
for (let i = 0; i < 1000; i++) {
result += Math.sqrt(Math.cos(Math.sin(base))) + base * base + base + base * 2 + base * 3
}
return result
},
},
}
</script>
总结
以上是我通过调查资料以及个人项目中的一些小经验得出的对于Vue性能优化的一些方案,可能文章中一些见解存在一些问题,欢迎大家在评论区指出,大家一起学习,一起进步。