vue项目性能优化总结
vue作为目前主流的前端框架,在github上拥有147kstar,作为vue开发大军的一员这篇文章分享一下自己使用过程中的主要针对vue项目的一些优化经验,欢迎大家一起交流(持续更新中......)一.代码优化1.v-if和v-show使用场景v-if 是条件渲染,条件为false时,不会在dom上渲染元素,条件判断的是否渲染该元素v-show 是显示与否,只是css层面上...
vue作为目前主流的前端框架,在github上拥有147kstar,作为vue开发大军的一员这篇文章分享一下自己使用过程中的主要针对vue项目的一些优化经验,欢迎大家一起交流(持续更新中......)
一.代码优化
1.v-if和v-show使用场景
v-if 是条件渲染,条件为false时,不会在dom上渲染元素,条件判断的是否渲染该元素
v-show 是显示与否,只是css层面上通过条件判断display的属性值
所以,v-if适用于运行时很少改变条件的情况,v-show适用于需要频繁切换条件的场景
2.v-for遍历添加key,并避免和v-if同时使用
使用v-for
更新已渲染的元素列表时,默认用就地复用
策略;列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素;给每一项item添加唯一的key可以更快定位diff,更高效的更新虚拟dom
v-for优先级高于v-if,同时使用意味着每一次遍历都要进行判断,影响速度
1.如果是根据遍历数据控制是否遍历当前元素,可以在computed中对v-for的数据进行过滤
2.如果是控制整个遍历数据的判断,可以把v-if写在v-for外层
3.合理使用computed 和 watch
一个值受多个值影响用computed,一个值影响多个值用watch
可以参考这篇文章https://blog.csdn.net/qq_24510455/article/details/81057261
4.异步组件(按需加载)
有时候需要根据某一个状态来判断到底使用哪个组件,这时候并不需要加载所有可能的组件,所以就要对组件进行按需加载
components: {
BindBank: () => import('./modules/bind-bank.vue'),
BindAilpay: () => import('./modules/bind-alipay.vue'),
Bindwechat: () => import('./modules/bind-wechat.vue'),
},
computed: {
componentAndNotice(){
let active = 0 // 0 1 2
let components = [BindBank,BindAilpay,Bindwechat]
return {
component: components[active],
}
}
}
5.长列表优化
在vue中,会对数据进行劫持,但是对于很多大量数据列表很多时候只是纯粹的数据显示,不需要做视图响应,为了减少组件初始化的时间,可以通过 Object.freeze 方法来冻结一个对象,一旦被冻结的对象就再也不能被修改了
6.状态管理
对于每个项目来说,状态管理是少不了的,对于vue项目来说,有很多种方式可以进行状态管理
1.父子组件之间---props和this.$emit()
2.vuex--适用于中大型SPA项目,所以千万不要一提到vue的状态管理就说vuex,也需要严格控制vuex中的数据,需要分析什么样的项目和数据需要使用vuex
3.Vue.observable( object )--2.6.0新增API
通过使用这个api我们可以应对一些简单的跨组件数据状态共享的情况。
store.js
import Vue from "vue";
export const store = Vue.observable({ count: 0 });
export const mutations = {
setCount(count) {
store.count = count;
}
};
在组件中引用
<template>
<div id="app">
<img width="25%" src="./assets/logo.png">
<p>count:{{count}}</p>
<button @click="setCount(count+1)">+1</button>
<button @click="setCount(count-1)">-1</button>
</div>
</template>
<script>
import { store, mutations } from "./store";
export default {
name: "App",
computed: {
count() {
return store.count;
}
},
methods: {
setCount: mutations.setCount
}
};
</script>
<style>
4.父孙组件之间-provide / inject--2.2.0新增API
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。只要在上一层级的声明的provide,那么下一层级无论多深都能够通过inject来访问到provide的数据,可以理解为加强版的props
这么做也是有明显的缺点的,在任意层级都能访问导致数据追踪比较困难,不知道是哪一个层级声明了这个或者不知道哪一层级或若干个层级使用了
<template>
<div class="test">
<son prop="data"></son>
</div>
</template>
<script>
export default {
name: 'Test',
provide: {
name: 'Garrett'
}
}
<template>
<div>
{{name}}
</div>
</template>
<script>
export default {
name: 'Grandson',
inject: [name]
}
</script>
5.发布订阅模式
属于行为型模式的一种,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态变化时,会通知所有的观察者对象,使他们能够自动更新自己。 vue的双向绑定原理就是数据劫持+发布订阅模式实现的
7.巧用作用域插槽
vue中插槽 分为 默认插槽、具名插槽、作用域插槽,对于前两种较为常用的就不多介绍,这里主要说一下作用域插槽
2.6.0以后作用域插槽和具名插槽统一使用v-slot,原有slot和slot-scope依然可以使用
作用域插槽:父组件能接收来自子组件的slot传递过来的参数,大概的使用场景,接口返回了一组列表数据,但是在不同页面可能需要不同的样式,如果写多套组件当然也没问题,但是一旦数据发生变化就意味着要维护大量的组件,这时候就可以考虑使用作用域插槽,简单来说就是子组件负责提供数据,父组件负责显示样式
//子组件
<template>
<div>
<ul>
<slot :data='item' v-for="(item,index) in list" :key="index"></slot>
</ul>
</div>
</template>
<script>
export default {
data () {
return {
list:['Webkit','Gecko','Trident','Blink']
}
}
}
</script>
// 父组件
<template>
<div>
<!--标准写法v-slot:default="a"->
<!--单个插槽可直接绑定在元素上 不需要<template v-slot="a"></template>-->
<child v-slot="a">
<li v-text="a.data"></li>
</child>
</div>
</template>
<script>
import child from './child';
export default {
name: 'app',
components: {
child
}
}
</script>
另外注意,如果使用v-slot,出现多个插槽必须使用完整的基于<template>的语法,
具名插槽写法由<div slot="header">可变为<div v-slot:"header"> 也可缩写为<div #header>
具体可参考https://cn.vuejs.org/v2/guide/components-slots.html
8.适当选择混入mixin
使用场景:比如同级的两个模块需要根据某个状态判断到底进入哪个,两个模块具有大部分相同的业务逻辑,可能都需要在某几个生命周期或者data中进行相同的操作,那这时候选择mixin会是一种好的方式
同名钩子函数将会混合为一个数组,都将被调用到,但是混入对象的钩子将在组件自身钩子之前调用。
mixin的数据对象和组件的数据发生冲突时以组件数据优先。
使用mixin会导致数据难以追踪,代码可读性也会较差,所以谨慎使用
9.不要给每个vue组件的style标签添加scoped
新建vue组件的时候会给style标签默认添加scoped属性,当编译之后会给dom节点加了一个不重复的data属性来表示唯一性,在css选择器末尾添加了data选择器来实现私有化(深度作用选择器),保证组件样式不相互污染。但是会导致组件样式权重加重,外部修改组件内部样式需要更高的权重,层级过深会极大增加复杂度,对于一个多人长期开发的项目来说,修改样式将会成为一个令人头疼的问题
建议对样式使用场景单一,很少来自外部的样式修改的组件使用scoped,其他组件建议使用和组件name属性相同的class对组件最外层包裹,内部样式以嵌套方式书写
10.及时销毁事件
Vue 组件销毁时,会自动清理它与其它实例的连接,解绑它的全部指令及事件监听器,但是仅限于组件本身的事件。如果在 js 内使用 addEventListene 等方式是不会自动销毁的,我们需要在组件销毁时手动移除这些事件的监听,以免造成内存泄露。为了准确移除监听,尽量不要使用匿名函数或者已有的函数的绑定来直接作为事件监听函数。
created() {
addEventListener('click', this.click, false)
},
beforeDestroy() {
removeEventListener('click', this.click, false)
}
二.webpack优化
1.图片压缩
使用image-webpack-loader压缩图片,并设置 limit 大小来对图片处理,对小于 limit 的图片转化为 base64 格式,其余的不做操作。
2.优化Loader配置
loader对文件的转换操作是很耗时的,所以尽量让更少的文件参与loader处理。
1.优化正则匹配,如果项目源码中只有js文件,就不要写成/\.jsx?$/,以提升正则表达式的性能 test: /\.js$/
2.通过cacheDirectory选项开启缓存,默认值是false。如果设置了这个参数,被转换的结果将会被缓存起来。当webpack再次编译时,将会首先尝试从缓存中读取转换结果,以此避免资源浪费。
3.通过include、exclude来减少被处理的文件
module.exports = {
module:{
rules:[
{
//如果项目源码中只有js文件,就不要写成/\.jsx?$/,以提升正则表达式的性能
test:/\.js$/,
//babel-loader支持缓存转换出的结果,webpack再次编译时,将会首先尝试从缓存中读取转换结果,以此避免资源浪费
loader:'babel-loader?cacheDirectory=ture',
//只在src文件夹下查找
include:[resolve('src')],
//不会去查找的路径
exclude:/node_modules/
}
]
}
}
3.优化resolve.alias配置
在导入语句没带文件后缀时,Webpack 会在自动带上后缀后去尝试询问文件是否存在。默认是:extensions :[‘. js ‘,’. json ’] 。也就是说,当遇到require ( '. /data ’)这样的导入语句时,Webpack会先去寻找./data .js 文件,如果该文件不存在,就去寻找./data.json 文件,如果还是找不到就报错。如果这个列表越长,或者正确的后缀越往后,就会造成尝试的次数越多,所以 resolve .extensions 的配置也会影响到构建的性能。
4.使用uglifyjs-webpack-plugin压缩代码文件
使用uglifyjs-webpack-plugin压缩代码文件,删除console语句、注释等
5.使用purgecss-webpack-plugin删除多余的css
对于一些大型老旧多人维护的项目,会发现很多css其实都已经是没有再使用,但是由于分布在很多文件中导致手动删除几乎很难完成,PurgeCSS 是一个能够通过字符串对比,来决定移除不需要的 CSS 的工具,同时也可以对不需要移出的添加白名单
具体可参考https://www.purgecss.com/with-webpack
6.优化SourceMap
我们在项目进行打包后,会将开发中的多个文件代码打包到一个文件中,并且经过压缩,去掉多余的空格,且babel编译化后,最终会用于线上环境,那么这样处理后的代码和源代码会有很大的差别,当有bug的时候,我们只能定位到压缩处理后的代码位置,无法定位到开发环境中的代码,对于开发不好调式,因此sourceMap出现了,它就是为了解决不好调式代码问题的。
webpack 在构建中提供了不少于7种的sourcemap模式,其中eval模式虽然可以提高构建效率,但是构建后的脚本较大,因此生产上并不适用。而source-map 模式可以通过生成的 .map 文件来追踪脚本文件的 具体位置,进而缩小脚本文件的体积,这是生产模式的首选,并且在生产中,我们需要隐藏具体的脚本信息,因此可以使用 cheap 和module 模式来达到目的。
7.开启Gzip压缩
gzip 是 GNUzip 的缩写,最早用于 UNIX 系统的文件压缩。HTTP 协议上的 gzip 编码是一种用来改进 web 应用程序性能的技术,web 服务器和客户端(浏览器)必须共同支持 gzip。目前主流的浏览器,Chrome,firefox,IE等都支持该协议。常见的服务器如 Apache,Nginx,IIS 同样支持,gzip 压缩效率非常高,通常可以达到 70% 的压缩率,也就是说,如果你的网页有 30K,压缩之后就变成了 9K 左右
以下为部分示例代码是基于cli3(node>=8 webpack=4)
千万不要忘记npm i ** -save-dev
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const BASE_URL = process.env.NODE_ENV === 'production' ? './' : './'
const path = require('path')
const CompressionPlugin = require("compression-webpack-plugin")
const isPro = process.env.NODE_ENV === 'production'
const resolve = dir => {
return path.join(__dirname, dir)
}
module.exports = {
publicPath: BASE_URL,
outputDir: 'dist', // 打包输出目录
assetsDir: 'assets', // 静态资源目录
indexPath: 'index.html',
filenameHashing: true,
lintOnSave: true,
// productionSourceMap: true, // 生产环境是否生成 sourceMap 文件 在cli3中好像并没有效果
chainWebpack: config => {
if (isPro) {
config.mode = 'production'
config.devtool = 'source-map' // sourceMap
return {
plugins: [new CompressionPlugin({
test: /\js$|\.htm1$|\.css/, // 匹配文件名
threshold: 10240, // 对超过10K的数据进行压缩
deleteriginalAssets: false // 是否删除原文件
})]
}
}
// 图片压缩插件
config.module
.rule('images')
.use('image-webpack-loader')
.loader('image-webpack-loader')
.options({
bypassOnDebug: true
})
.end()
config.module
.rule('images')
.use('url-loader')
.loader('url-loader')
.options({
limit: 1024 * 500 // 图片文件高于500K不转base64
})
// key,value自行定义,比如.set('@@', resolve('src/components'))
config.resolve.alias
.set('@', resolve('src')) // @在cli3中已内置,也可重定义
.set('_c', resolve('src/components'))
},
configureWebpack: {
optimization: {
minimizer: [
new UglifyJsPlugin({
uglifyOptions: {
beautify: false, // 最紧凑的输出
comments: false, // 删除所有的注释
compress: {
drop_console: true, // console
drop_debugger: false,
pure_funcs: ['console.log'] // 移除console
}
}
})
]
}
}
}
三.web优化
1.使用CDN
使用cdn可以使下载文件的并发连接数大大增加,且CDN 具有更好的可用性,更低的网络延迟和丢包率 。
对于项目中使用的一些静态资源可以选择使用cdn来加载,比如jq、axios等工具库、element等大体积的ui库
推荐两个静态资源库
更多推荐
所有评论(0)