笔者前面写了好多关于vue的文章(专栏也有两个),有关于vue源码部分的,也有笔者总结的实战demo。但其实,笔者一直想分享出来的,也是希望给自己做个持续总结的,还是关于性能优化和代码规范方面的。
笔者曾经完成过学院社团官网的开发,当时第一版乃至第三版在性能上都极其差劲,所以,这大概是笔者的“心结”。但不得不说,性能优化能为网站(项目)带来非常美妙的体验感,而使用同一的风格规范,可以在绝大多数工程中改善代码的可读性和后续的开发体验。
笔者曾经解释过,key这个属性的主要用途就是vue的虚拟DOM算法中,在对比新旧虚拟节点时便于辨识虚拟节点。
在更新子节点时,需要从旧虚拟节点列表中查找与新虚拟节点相同的节点进行更新。如果这个查找过程设置了属性key,则查找速度会快很多 —— 设置了“唯一性标识”(这也说明了为什么要设置“动态属性”)。笔者建议大家无论如何尽可能的在使用v-for
时提供key
(除非遍历输出的DOM内容非常简单):
<div v-for="item in items" :key="item.id">
<!-- 内容 -->
</div>
相比之下同是数据渲染的微信小程序就在这一点上做了“努力”:如果你的for遍历没有带key,它会给出警告。而其微信小程序还专门出了一个属性
wx:for-index="xxxx"
,就是为了获取循环中的“下标”
如果一组v-if+v-else
的元素类型相同(比如两个div元素),最好还是使用属性key吧!
前面笔者介绍过template模板编译——其中v-if编译后是这个样子:
(has)
? _c('li',[_v("if")])
: _c('li',[_v("else")])
故而当状态发生改变时,生成的虚拟节点既有可能是v-if上的虚拟节点,也有可能是v-else上的虚拟节点。
“默认情况下,vue会尽可能高效地更新DOM”。这意味着,当它在相同类型的元素之间切换时,会修补已存在的元素,而不是将旧的元素移除,然后在同一位置添加一个新元素。如果本不相同的元素被识别为相同,会出现意料之外的作用。
但如果添加了属性key,那么在比对虚拟DOM时,则会认为它们是两个不同的节点,于是会将旧元素移除并在相同的位置添加一个新元素,从而避免“副作用”:
<div v-if="error" key="search-status">
错误:{{error}}
</div>
<div v-else key="search-results">
{{results}}
</div>
在使用vue开发项目时,最常遇到的一个问题就是:当页面切换到同一个路由但不同参数的地址时,组件的生命周期并不会触发。
例如下面的路由:
const routes=[
{
path:'/detail/:id',
name:'detail',
component:Detail
}
]
当我们从路由/detail/1
切换到/detail/2
时,组件是不会发生任何变化的。
这是因为vue-router会识别出两个路由使用的是同一个组件,从而进行“复用”,并不会重新创建组件。
当然,组件本质上是一个映射关系,所以先销毁再创建一个相同的组件会造成很大程度上的性能浪费,复用组件无疑是个正确的选择。
下面是几种常用的解决方法:
1、路由导航守卫beforeRouteUpdate
vue提供了导航守卫beforeRouteUpdate,该守卫在当前路由改变且组件被复用时调用。我们可以在其中发送请求拉取数据,更新状态并重新渲染视图。比如:
beforeRouteUpdate: (to, from, next) => {
if(this.$router.currentRoute.path.include('/detail')){
this.mxcUpdate(); //调用发请求函数
}
next();
}
2、组件导航守卫中设置对应的meta属性
这种方法针对的是vue的“缓存机制”——
beforeRouteEnter: (to, from, next) => { // 写在当前组件
to.meta.keepAlive = false
next()
},
beforeRouteLeave: (to, from, next) => { //写在前一个组件
to.meta.keepAlive = false
next()
},
3、观察$route对象的变化
所谓“观察”,即为“监听”:
这种方式的代价是组件内多了一个watch
—— 这会带来依赖追踪的内存开销。
假设有这样一个场景:页面中有两部分内容,上面是个人的描述信息,下面是一个带翻页的列表,而且你是这样设置路由参数的: /user?id=3&page=1
,说明用户ID是3,当前列表是第1页。
我们因此断定列表翻页时只需发送列表的请求——改变列表的参数。此时,id应该是不变的。
这里,就可以设置以减少请求的内存消耗:
watch:{
"$route.query.id" () {
// 请求个人描述信息
},
"$route.query.page" () {
// 请求哩啊表
}
}
——而不是选择统一观察$route。
当然,你也可以选择【监听path】:
watch:{
'$route'(){
if(this.$route.path==='test'){
this.test();
}
}
}
4、为router-view组件添加属性key
这种做法很有效,也很暴力。它本质上是利用了虚拟DOM渲染时通过key来比对两个节点是否相同的原理:
<router-view :key="$route.fullPath"></router-view>
千万,千万,千万不要把这两个指令放在一起(一个元素上)。
经笔者试验,v-for
的效果会覆盖v-if
的效果。
官方给出的解释是:当Vue.js处理指令时,v-for比v-if具有更高的优先级,所以即使我们只渲染出列表的一小部分元素,也得在每次重渲染的时候遍历整个列表,而不考虑活跃用户是否发生了变化。
通常,我们在遇到如下面的情况,会有不同的做法:
v-for="user in users" v-if="user.isActive"
,可以将users替换为一个计算属性,让它返回过滤后的列表css的规则都是全局的——当你引入之后,任何一个组件的样式规则对整个页面都有效。
所以,在vue中,产生了scoped
,这是我们现在写大多数中小型项目时首先想到的:
vue中还有一个特性:CSS Module
——一个机遇class的类似BEM的策略
其实,对于组件库,我们应该更倾向于选用机遇class的策略而不是scoped特性。因为机遇class的策略使覆写内部样式更容易,它使用容易理解的class名称且没有太高的选择器优先级,不容易起冲突,而且保密性得到了提升。
module
还经常和webpack一起用 —— 在webpack中vue-loader.config.js文件中添加代码:
cssModules:{
localIndentName:'[hash:base64:5]', //编译后的classname
camelCass:true //采用驼峰命名法(编译时自动转化)
}
哦,对了:避免在scoped中使用元素选择器
对某一常用变量——比如css中的background,我们会想到将其单独放在一个文件中,那这个文件所在文件夹极为常用,我们怎么方便的寻找:起别名
注:cli中“@”指src目录
比如:build > webpack.base.conf.js >> resolve >> alias
添加:'styles': resolve('src/assets/styles')
然后我们再去引用src/assets/styles目录下的样式文件,就可以直接用如:import '~styles/xxx.css'