不可错过的 vue 开发技巧

原创
开发 前端
本文主要介绍日常项目开发过程中的一些技巧,帮助大家规避错误的同时还能提高应用的性能。以下是我总结一些平时工作中的经验。

【51CTO.com原创稿件】

前言

本文主要介绍日常项目开发过程中的一些技巧,帮助大家规避错误的同时还能提高应用的性能。以下是我总结的一些平时工作中的经验。

在v-if/v-if-else/v-else中使用key

如果一组v-if 与v-else的元素类型相同,最好使用属性key。这是因为Vue2.0引入虚拟DOM,为了避免不必要的DOM操作,虚拟DOM在虚拟节点映射到视图过程中,将虚拟节点与上一次渲染视图所使用的旧虚拟节点做对比,找出真正需要更新的节点来进行DOM操作。但有时如果两个本不相同的节点被识别为相同,便会出现意料之外的问题。我们看下面的一个例子:

  1. // 这种写法会出bug 
  2. <div v-if="flag"
  3.   <label>浪里行舟</label> 
  4.   <input type="text" /> 
  5. </div> 
  6. <div v-else
  7.   <label>前端工匠</label> 
  8.   <input type="text" /> 
  9. </div> 
  10. <button @click="flag = !flag">切换</button>  

如果添加了属性key,那么在对比虚拟DOM时,则会认为它们是两个不同的节点,于是会将旧元素移除并在相同位置添加新元素,从而避免漏洞的出现。

  1. // 最佳写法 
  2. <div v-if="flag"
  3.   <label>浪里行舟</label> 
  4.   <input key="1" type="text" /> 
  5. </div> 
  6. <div v-else
  7.   <label>前端工匠</label> 
  8.   <input key="2" type="text" /> 
  9. </div> 
  10. <button @click="flag = !flag">切换</button> 

v-for循环中不要使用index作为key

我们会给列表渲染设置属性key,这个key属性主要用在虚拟DOM算法上,在对比新旧虚拟节点时辨识虚拟节点。但如果key用得不合理,就会出现bug,比如我们使用index作为key(见下面例子),核心代码如下:

  1. <div class="border"
  2.   <Children v-for="(key, index) in list" :key="index">//使用index作为key 
  3.     <button @click="() => handleDelete(key)">删除</button> 
  4.   </Children> 
  5.   <button @click="handleAdd">添加</button> 
  6. </div> 
  7. ...... 
  8.   handleAdd() { 
  9.     this.list.push(key++); 
  10.   }, 
  11.   handleDelete(key) { 
  12.     const index = this.list.findIndex(k => k === key); 
  13.     this.list.splice(index, 1); 
  14.   } 

上例中,我们想删除第二个输入框,却误删了第三个输入框,这因为当使用splice()方法删除数组的某个元素时数组的index会被重新索引,造成数组的最后一个index丢失,从而会使虚拟DOM的最后一个结点(key)丢失,造成无论删除哪个结点都会误删除最后一个结点的bug。但如果我们使用传入的key作为key,就可以避免这种问题出现

  1. <div class="border"
  2.   <Children v-for="key in list" :key="key"
  3.     <button @click="() => handleDelete(key)">删除</button> 
  4.   </Children> 
  5.   <button @click="handleAdd">添加</button> 
  6. </div> 

简单暴力的router key

我们在项目开发时,可能会遇到这样问题:当页面切换到同一个路由但不同参数地址时,比如/detail/1,跳转到/detail/2,页面跳转后数据竟然没更新?路由配置如下:

  1.     path: "/detail/:id"
  2.     name:"detail"
  3.     component: Detail 

这是因为vue-router会识别出两个路由使用的是同一个组件从而进行复用,并不会重新创建组件,而且组件的生命周期钩子自然也不会被触发,导致跳转后数据没有更新。那我们如何解决这个问题呢? 我们可以为router-view组件添加属性key,例子如下: 

  1. <router-view :key="$route.fullpath"></router-view

这种办法主要是利用虚拟DOM在渲染时候通过key来对比两个节点是否相同,如果key不相同,就会判定router-view组件是一个新节点,从而先销毁组件,然后再重新创建新组件,这样组件内的生命周期会重新触发。

路由懒加载

当打包构建应用时,JavaScript 包会变得非常大,影响页面加载。如果我们能把不同路由对应的组件分割成不同的代码块,然后当路由被访问的时候才加载对应组件,这样就更加高效了。 配合webpack支持的路由懒加载方法有:

  • 这种方法比较通用,而且支持性好

  1. resolve => require([./Foo], resolve) 
  1. const Foo = () => import('./Foo'

接下来我们以官方文档的写法为例,对比这两种写法:

  1. // 非懒加载写法 
  2. import Vue from 'vue' 
  3. import Router from 'vue-router' 
  4. import Home from 'pages/home' 
  5. ... 
  6. ​ 
  7. Vue.use(Router) 
  8. ​ 
  9. export default new Router({ 
  10. routes: [ 
  11.   { 
  12.       path: '/'
  13.       name'home'
  14.       component: Home 
  15.   } 
  16.   ... 
  17. }) 

推荐以下写法,路由懒加载可以帮我们在进入首屏时不用加载过多的资源,从而减少首屏加载速度。

  1. // 路由懒加载写法 
  2. import Vue from 'vue' 
  3. import Router from 'vue-router' 
  4. ​ 
  5. // 其它都不用变,就是这么简单 
  6. const Home = () => import('./home'
  7. ... 
  8. ​ 
  9. Vue.use(Router) 
  10. ​ 
  11. export default new Router({ 
  12. routes: [ 
  13.   { 
  14.       path: '/'
  15.       name'home'
  16.       component: Home 
  17.   } 
  18.   ... 
  19. }) 

不要在使用v-for的同一元素上使用v-if

Vue官方文档强烈建议永远不要把 v-if 和 v-for 同时用在同一个元素上。一般我们在两种常见的情况下会倾向于这样做:

  • 1)为了过滤一个列表中的项目 (比如 v-for="user in users" v-if="user.isActive")。在这种情形下,请将 users 替换为一个计算属性 (比如 activeUsers),让其返回过滤后的列表(见下面例子)。

  1. // 第一种情形 反例 
  2. <ul> 
  3. <li 
  4.   v-for="user in users" 
  5.   v-if="user.isActive" 
  6.   :key="user.id" 
  7.   {{ user.name }} 
  8. </li> 
  9. </ul> 

当 Vue 处理指令时,v-for 比 v-if 具有更高的优先级,所以哪怕我们只渲染出一小部分用户的元素,也得在每次重渲染的时候遍历整个列表,不论活跃用户是否发生了变化。我们可通过将其更换为在如下的一个计算属性上遍历:

  1. // 好例子 
  2. computed: { 
  3. activeUsers: function () { 
  4.   return this.users.filter(function (user) { 
  5.     return user.isActive 
  6.   }) 
  1. <ul> 
  2. <li 
  3.   v-for="user in activeUsers" 
  4.   :key="user.id" 
  5.   {{ user.name }} 
  6. </li> 
  7. </ul> 
  • 2)为了避免渲染本应该被隐藏的列表 (比如 v-for="user in users" v-if="shouldShowUsers")。这种情形下,请将 v-if 移动至容器元素上 (比如 ul, ol)(见下面例子)。

  1. // 第二种情形 反例 
  2. <ul> 
  3. <li 
  4.   v-for="user in users" 
  5.   v-if="shouldShowUsers" 
  6.   :key="user.id" 
  7.   {{ user.name }} 
  8. </li> 
  9. </ul> 

更新为:

  1. // 好例子 
  2. <ul v-if="shouldShowUsers"
  3. <li 
  4.   v-for="user in users" 
  5.   :key="user.id" 
  6.   {{ user.name }} 
  7. </li> 
  8. </ul> 

过滤器让数据处理更便利

Vue.js 允许你自定义过滤器,它的用法其实是很简单,但是可能有些朋友没有用过,接下来我们介绍下:

1.理解过滤器

  • 功能:对要显示的数据进行特定格式化后再显示

  • 注意:过滤器并没有改变原本的数据,需要对展现的数据进行包装

  • 使用场景:双花括号插值和 v-bind 表达式 (后者从 2.1.0+ 开始支持)。

2.定义过滤器

可以在一个组件的选项中定义本地的过滤器:

  1. filters: { 
  2. capitalize: function (value) { 
  3.   if (!value) return '' 
  4.   value = value.toString() 
  5.   return value.charAt(0).toUpperCase() + value.slice(1) 

也可以在创建 Vue 实例之前全局定义过滤器:

  1. Vue.filter('capitalize'function (value) { 
  2. if (!value) return '' 
  3. value = value.toString() 
  4. return value.charAt(0).toUpperCase() + value.slice(1) 
  5. }) 

3.使用过滤器

使用方法也简单,即在双花括号中使用管道符(pipeline) |隔开:

  1. <!-- 在双花括号中 --> 
  2. <div>{{ myData| filterName}}</div> 
  3. <div>{{ myData| filterName(arg)}}</div> 
  4. <!-- 在 v-bind 中 --> 
  5. <div v-bind:id="rawId | formatId"></div> 

接下来我们看个如何使用过滤器格式化日期的例子:

  1. <div> 
  2.   <h2>显示格式化的日期时间</h2> 
  3.   <p>{{ date }}</p> 
  4.   <p>{{ date | filterDate }}</p> 
  5.   <p>年月日: {{ date | filterDate("YYYY-MM-DD") }}</p> 
  6. </div> 
  7. ...... 
  8. filters: { 
  9.   filterDate(value, format = "YYYY-MM-DD HH:mm:ss") { 
  10.     console.log(this)//undefined 过滤器没有this指向的 
  11.     return moment(value).format(format); 
  12.   } 
  13. }, 
  14. data() { 
  15.   return { 
  16.     date: new Date() 
  17.   }; 

 

能用computed的尽量用computed代替 watch

很多时候页面会出现 watch 的滥用而导致一系列问题的产生,而通常更好的办法是使用 computed 属性,先从一张图区别它们有什么区别?

从上面流程图中,我们可以看出它们之间的区别:

  • watch:监测的是属性值, 只要属性值发生变化,其都会触发执行回调函数来执行一系列操作。

  • computed:监测的是依赖值,依赖值不变的情况下其会直接读取缓存进行复用,变化的情况下才会重新计算。

除此之外,有点很重要的区别是:计算属性不能执行异步任务,计算属性必须同步执行。也就是说计算属性不能向服务器请求或者执行异步任务。如果遇到异步任务,就交给侦听属性。watch也可以检测computed属性。总而言之,两者的区别归纳为以下两句话:

  • computed能做的,watch都能做,反之则不行

  • 能用computed的尽量用computed

为啥提倡使用 computed 代替 watch,这是因为有时候可以实现同样的效果,而 computed 会更胜一筹,比如在处理多数据联动的情况下,使用 computed 会更加合理一点。

  1. <template> 
  2.   <div> 
  3.       <input type="text" v-model="firstName"
  4.       <input type="text" v-model="lastName"
  5.       <span>{{ fullName }}</span> 
  6.       <span>{{ fullName2 }}</span> 
  7.   </div> 
  8. </template> 
  9. ​ 
  10. <script> 
  11. export default { 
  12.   data() { 
  13.       reurn { 
  14.           firstName: ''
  15.           lastName: ''
  16.           fullName2: '' 
  17.       } 
  18.   }, 
  19.     
  20.   // 使用 computed 
  21.   computed: { 
  22.       fullName() { 
  23.           return this.firstName + ' ' + this.lastName 
  24.       } 
  25.   }, 
  26.     
  27.   // 使用 watch 
  28.   watch: { 
  29.       firstName: function(newVal, oldVal) { 
  30.           this.fullName2 = newVal + ' ' + this.lastName; 
  31.       }, 
  32.       lastName: function(newVal, oldVal) { 
  33.           this.fullName2 = this.firstName + ' ' + newVal; 
  34.       }, 
  35.   } 
  36. </script> 

化繁为简的Watchers

如果我们需要在组件初始化以及侦听属性变化时调用同一个方法,通常的做法像下面这样:

  1. watch: { 
  2. myProperty() { 
  3.   this.doSomething(); 
  4. }, 
  5. created() { 
  6. this.doSomething(); 
  7. }, 
  8. methods: { 
  9. doSomething() { 
  10.     console.log('doing something...'); 

尽管上面这段代码看起来没什么问题,但created钩子里面执行的方法是多余的。我们可以把所需要执行的方法放到watch里面,避免在created钩子里写重复代码,那将会在组件实例化的时候触发多一次。 那如何优化呢?代码如下:

  1. watch: { 
  2. myProperty: { 
  3.   immediate: true,//表示创建组件时立马执行一次 
  4.   handler() { 
  5.     console.log('doing something...'); // 只用一次的方法没必要在methods里面声明了 
  6.   } 

参考文章与书籍

作者介绍

浪里行舟,慕课网认证作者,前端爱好者,立志往全栈工程师发展,从事前端一年多,目前技术栈有vue全家桶、ES6以及less等,乐于分享,最近一年写了五六十篇原创技术文章,得到诸多好评!

【51CTO原创稿件,合作站点转载请注明原文作者和出处为51CTO.com】

 

责任编辑:庞桂玉 来源: 51CTO
相关推荐

2015-07-06 10:09:33

iosFoundationNSHashTable

2015-10-21 13:42:54

iOS开发watch OS2

2015-07-07 10:15:56

iOSUIVisualEffweak

2015-07-07 14:05:22

iOS技巧

2014-07-23 10:08:34

Angular前端项目

2021-04-21 07:51:06

Vue 开发VS CodeVetur

2024-01-09 18:01:38

2021-10-27 08:00:00

DevSecOps开发安全

2015-07-28 20:34:01

Android开发框架

2016-10-25 14:27:16

开源Ruby on RaiWeb框架

2016-12-01 08:36:18

编程云环境云战略

2019-04-09 15:12:43

开发者技能工具

2020-06-30 08:28:29

Vue开发前端

2020-03-05 12:12:54

数据Python开发

2018-10-23 10:35:20

react.jsReact面试题前端

2022-12-15 16:38:17

2021-01-05 05:15:02

Github 前端仓库

2015-04-01 10:55:55

2022-04-15 09:01:18

前端工具UTF8编码

2015-06-10 10:56:50

iOS开发技巧
点赞
收藏

51CTO技术栈公众号