深入浅出vue:实战中的性能优化和代码规范(持续更新)

笔者前面写了好多关于vue的文章(专栏也有两个),有关于vue源码部分的,也有笔者总结的实战demo。但其实,笔者一直想分享出来的,也是希望给自己做个持续总结的,还是关于性能优化和代码规范方面的。
笔者曾经完成过学院社团官网的开发,当时第一版乃至第三版在性能上都极其差劲,所以,这大概是笔者的“心结”。但不得不说,性能优化能为网站(项目)带来非常美妙的体验感,而使用同一的风格规范,可以在绝大多数工程中改善代码的可读性和后续的开发体验。


为列表渲染设置属性key

笔者曾经解释过,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-if-else/v-else中使用key

如果一组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的“缓存机制”——<keep-alive>

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-if和v-for一起使用

千万,千万,千万不要把这两个指令放在一起(一个元素上)。
经笔者试验,v-for的效果会覆盖v-if的效果。
官方给出的解释是:当Vue.js处理指令时,v-for比v-if具有更高的优先级,所以即使我们只渲染出列表的一小部分元素,也得在每次重渲染的时候遍历整个列表,而不考虑活跃用户是否发生了变化。

通常,我们在遇到如下面的情况,会有不同的做法:

  1. 比如有v-for="user in users" v-if="user.isActive",可以将users替换为一个计算属性,让它返回过滤后的列表
  2. 我们还可以将v-if移至上层容器元素上(如ol或ul)

为组件样式设置作用域

css的规则都是全局的——当你引入之后,任何一个组件的样式规则对整个页面都有效。
所以,在vue中,产生了scoped,这是我们现在写大多数中小型项目时首先想到的:

<template>
	<button class="button buttonClose"></button>
</template>
<style scoped>
	.button{
		border:none;
		border-radius:5px;
	}
	.button-close{
		background-color:green;
	}
</style>

vue中还有一个特性:CSS Module——一个机遇class的类似BEM的策略
其实,对于组件库,我们应该更倾向于选用机遇class的策略而不是scoped特性。因为机遇class的策略使覆写内部样式更容易,它使用容易理解的class名称且没有太高的选择器优先级,不容易起冲突,而且保密性得到了提升。

<template>
	<button :class="[$style.button, $style.buttonClose]"></button>
</template>
<style module>
	.button{
		border:none;
		border-radius:5px;
	}
	.button-close{
		background-color:green;
	}
</style>

module还经常和webpack一起用 —— 在webpack中vue-loader.config.js文件中添加代码:

cssModules:{
	localIndentName:'[hash:base64:5]',   //编译后的classname
	camelCass:true   //采用驼峰命名法(编译时自动转化)
}

哦,对了:避免在scoped中使用元素选择器

发布了200 篇原创文章 · 获赞 417 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/qq_43624878/article/details/104402743