阵阵键盘声,隐隐测试言。产品不稳固,今夜无人还。

在开发Vue的过程中,咱们常常会遇到一些这样那样的问题,而后要卡好半天,等问题解决了才发现原来一些细节知识点还是没有把握好。明天小编就整顿了几个在我的项目中会用到的一些实战技巧点,心愿能够帮忙到正在致力赚钱的你。江湖规矩,先赞后看,艳遇一直。

数据不响应,可能是用法有问题

前几天有敌人给我发了一段代码,而后说Vue有bug,他明明写的没问题,为啥数据就不响应呢,肯定是Vue的bug?我感觉他比尤雨溪要牛逼,高攀不起,就没有理他了。然而的确有时候咱们在开发时候会遇到数据不响应的状况,那怎么办呢?比方上面这段代码:

<template>  <div>    <div>      <span>用户名: {{ userInfo.name }}</span>      <span>用户性别: {{ userInfo.sex }}</span>      <span v-if="userInfo.officialAccount">        公众号: {{ userInfo.officialAccount }}      </span>    </div>    <button @click="handleAddOfficialAccount">增加公众号</button>  </div></template><script>export default {  data() {    return {      userInfo: {        name: '子君',        sex: '男'      }    }  },  methods: {    // 在这里增加用户的公众号    handleAddOfficialAccount() {      this.userInfo.officialAccount = '前端有的玩'    }  }}</script>

在下面的代码中,咱们心愿给用户信息外面增加公众号属性,然而通过this.userInfo.officialAccount = '前端有的玩' 增加之后,并没有失效,这是为什么呢?

这是因为在Vue外部,数据响应是通过应用Object.definePrototype监听对象的每一个键的getter,setter办法来实现的,但通过这种办法只能监听到已有属性,新增的属性是无奈监听到的,但我就是想监听,小编你说咋办吧。上面小编提供了四种形式,如果有更多形式,欢送下方评论区通知我。

1. 将原本要新增的属性提前在data中定义好

比方下面的公众号,我能够提前在userInfo外面定义好,这样就不是新增属性了,就像上面这样

data() {    return {      userInfo: {        name: '子君',        sex: '男',        // 我先提前定义好        officialAccount: ''      }    }  }

2. 间接替换掉userInfo

尽管无奈给userInfo外面增加新的属性,然而因为userInfo曾经定义好了,所以我间接批改userInfo的值不就能够了么,所以也能够像上面这样写

this.userInfo = {  // 将原来的userInfo 通过扩大运算法复制到新的对象外面  ...this.userInfo,  // 增加新属性  officialAccount: '前端有的玩'}

3. 应用Vue.set

其实下面两种办法都有点取巧的嫌疑,其实对于新增属性,Vue官网专门提供了一个新的办法Vue.set用来解决新增属性无奈触发数据响应。

Vue.set 办法定义

/*** target 要批改的对象* prpertyName 要增加的属性名称* value 要增加的属性值*/Vue.set( target, propertyName, value )

下面的代码应用Vue.set能够批改为

import Vue from 'vue'// 在这里增加用户的公众号handleAddOfficialAccount() {  Vue.set(this.userInfo,'officialAccount', '前端有的玩')}

然而每次要用到set办法的时候,还要把Vue引入进来,好麻烦,所以为了简便起见,Vue又将set办法挂载到了Vue的原型链上了,即Vue.prototype.$set = Vue.set,所以在Vue组件外部能够间接应用this.$set代替Vue.set

this.$set(this.userInfo,'officialAccount', '前端有的玩')

小编发现有许多同学不晓得什么时候应该用Vue.set,其实只有当你要赋值的属性还没有定义的时候须要应用Vue,set,其余时候个别不会须要应用。

4. 应用$forceUpdate

我感觉$forceUpdate的存在,让许多前端开发者不会再去留神数据双向绑定的原理,因为不论什么时候,反正我批改了data之后,调用一下$forceUpdate就会让Vue组件从新渲染,bug是不会存在的。然而实际上这个办法并不倡议应用,因为它会引起许多不必要的性能耗费。

针对数组的特定形式

其实不仅仅是对象,数组也存在数据批改之后不响应的状况,比方上面这段代码

<template>  <div>    <ul>      <li v-for="item in list" :key="item">        {{ item }}      </li>    </ul>    <button @click="handleChangeName">批改名称</button>  </div></template><script>export default {  data() {    return {      list: ['张三', '李四']    }  },  methods: {    // 批改用户名称    handleChangeName() {      this.list[0] = '王五'    }  }}</script>

下面的代码心愿将张三的名字批改为王五,实际上这个批改并不能失效,这是因为Vue不能检测到以下变动的数组:

  1. 当你利用索引间接设置一个项时,例如: this.list[index] = newValue
  2. 批改数组的length属性,例如: this.list.length = 0

所以在上例中通过this.list[0] = '王五' 是无奈触发数据响应的,那应该怎么办呢?像下面提到的Vue.set和$forceUpdate都能够解决这个问题,比方Vue.set能够这样写

Vue.set(this.list,0,'王五')复制代码

除了那些办法之外,Vue还针对数组提供了变异办法

在操作数组的时候,咱们个别会用到数据提供的许多办法,比方push,pop,splice等等,在Vue中调用数组下面提供的这些办法批改数组的值是能够触发数据响应的,比方下面的代码改为以下代码即可触发数据响应

this.list.splice(0,1,'王五')

实际上,如果Vue仅仅依赖getter与setter,是无奈做到在数组调用push,pop等办法时候触发数据响应的,因而Vue实际上是通过劫持这些办法,对这些办法进行包装变异来实现的。

Vue对数组的以下办法进行的包装变异:

  • push
  • pop
  • shift
  • unshift
  • splice
  • sort
  • reverse

所以在操作数组的时候,调用下面这些办法是能够保证数据能够失常响应,上面是Vue源码中包装数组办法的代码:

var original = arrayProto[method];  def(arrayMethods, method, function mutator () {    // 将 arguments 转换为数组    var args = [], len = arguments.length;    while ( len-- ) args[ len ] = arguments[ len ];    var result = original.apply(this, args);    // 这儿的用法同dependArray(value),就是为了获得dep    var ob = this.__ob__;    var inserted;    switch (method) {      case 'push':      case 'unshift':        inserted = args;        break      case 'splice':        inserted = args.slice(2);        break    }    // 如果有新的数据插入,则插入的数据也要进行一个响应式    if (inserted) { ob.observeArray(inserted); }   // 告诉依赖进行更新    ob.dep.notify();    return result  });

文本格式化,filter更简略

应用filter 简化逻辑

我想把工夫戳显示成yyyy-MM-DD HH:mm:ss的格局怎么办?是须要在代码中先将日期格式化之后,再渲染到模板吗?就像上面这样

<template>  <div>    {{ dateStr }}    <ul>      <li v-for="(item, index) in getList" :key="index">        {{ item.date }}      </li>    </ul>  </div></template><script>import { format } from '@/utils/date'export default {  data() {    return {      date: Date.now(),      list: [        {          date: Date.now()        }      ]    }  },  computed: {    dateStr() {      return format(this.date, 'yyyy-MM-DD HH:mm:ss')    },    getList() {      return this.list.map(item => {        return {          ...item,          date: format(item.date, 'yyyy-MM-DD HH:mm:ss')        }      })    }  }}</script>

像下面的写法,针对每一个日期字段都须要调用format,而后通过计算属性进行转换?这时候能够思考应用Vue提供的filter去简化

<template>  <div>    <!--应用过滤器-->    {{ dateStr | formatDate }}    <ul>      <li v-for="(item, index) in list" :key="index">        <!--在v-for中应用过滤器-->        {{ item.date | formatDate }}      </li>    </ul>  </div></template><script>import { format } from '@/utils/date'export default {  filters: {    formatDate(value) {      return format(value, 'yyyy-MM-DD HH:mm:ss')    }  },  data() {    return {      date: Date.now(),      list: [        {          date: Date.now()        }      ]    }  }}</script>

通过下面的批改是不是就简略多了

注册全局filter

有些过滤器应用的很频繁,比方下面提到的日期过滤器,在很多中央都要应用,这时候如果在每一个要用到的组件外面都去定义一遍,就显得有些多余了,这时候就能够思考Vue.filter注册全局过滤器

对于全局过滤器,个别倡议在我的项目外面增加filters目录,而后在filters目录外面增加

// filters\index.jsimport Vue from 'vue'import { format } from '@/utils/date'Vue.filter('formatDate', value => {  return format(value, 'yyyy-MM-DD HH:mm:ss')})

而后将filters外面的文件引入到main.js外面,这时候就能够在组件外面间接用了,比方将后面的代码能够批改为

<template>  <div>    <!--应用过滤器-->    {{ dateStr | formatDate }}    <ul>      <li v-for="(item, index) in list" :key="index">        <!--在v-for中应用过滤器-->        {{ item.date | formatDate }}      </li>    </ul>  </div></template><script>export default {  data() {    return {      date: Date.now(),      list: [        {          date: Date.now()        }      ]    }  }}</script>

是不是更简略了

开发了插件库,来装置一下

在应用一些UI框架的时候,常常须要应用Vue.use来装置, 比方应用element-ui时候,常常会这样写:

import Vue from 'vue';import ElementUI from 'element-ui';import 'element-ui/lib/theme-chalk/index.css';Vue.use(ElementUI,{size: 'small'});

应用了Vue.use之后,element-ui就能够间接在组件外面应用了,好神奇哦(呸,娘炮)。接下来咱们实现一个简化版的element来看如何去装置。

理解Vue.use的用法

Vue.use是一个全局的办法,它须要在你调用 new Vue() 启动利用之前实现,Vue.use的参数如下

/*** plugin: 要装置的插件 如 ElementUI* options: 插件的配置信息 如 {size: 'small'}*/Vue.use(plugin, options)

模仿element-ui的装置逻辑

想一下,应用Vue.use(ElementUI,{size: 'small'}) 之后咱们能够用到哪些element-ui提供的货色

  1. 能够间接在组件外面用element-ui的组件,不须要再import
  2. 能够间接应用v-loading指令
  3. 通过this.$loading在组件外面显示loading
  4. 其余...
// 这个是一个按钮组件import Button from '@/components/button'// loading 指令import loadingDirective from '@/components/loading/directive'// loading 办法import loadingMethod from '@/components/loading'export default {  /**   * Vue.use 须要插件提供一个install办法   * @param {*} Vue Vue   * @param {*} options 插件配置信息   */  install(Vue, options) {    console.log(options)    // 将组件通过Vue.components 进行注册    Vue.components(Button.name, Button)    // 注册全局指令    Vue.directive('loading', loadingDirective)    // 将loadingMethod 挂载到 Vue原型链下面,不便调用    Vue.prototype.$loading = loadingMethod  }}

通过下面的代码,曾经实现了一个丐版的element-ui插件,这时候就能够在main.js外面通过Vue.use进行插件装置了。大家可能会有疑难,为什么我要用这种写法,不必这种写法我照样能够实现性能啊。小编认为这种写法有两个劣势

  1. 标准化,通过提供一种对立的开发模式,无论对插件开发者还是使用者来说,都有一个标准去遵循。
  2. 插件缓存,Vue.use 在装置插件的时候,会对插件进行缓存,即一个插件如果装置屡次,实际上只会在第一次装置时失效。

插件的利用场景

  1. 增加全局办法或者 property。
  2. 增加全局资源:指令/过滤器/过渡等。
  3. 通过全局混入来增加一些组件选项。
  4. 增加 Vue 实例办法,通过把它们增加到 Vue.prototype 上实现。
  5. 一个库,提供本人的 API,同时提供下面提到的一个或多个性能。如element-ui

进步Vue渲染性能,理解一下Object.freeze

当一个 Vue 实例被创立时,它将 data 对象中的所有的 property 退出到 Vue 的响应式零碎中。当这些 property 的值产生扭转时,视图将会产生“响应”,即匹配更新为新的值。然而这个过程实际上是比拟耗费性能的,所以对于一些有大量数据但只是展现的界面来说,并不需要将property退出到响应式零碎中,这样能够进步渲染性能,怎么做呢,你须要理解一下Object.freeze。

在Vue官网中,有这样一段话:这里惟一的例外是应用 Object.freeze(),这会阻止批改现有的 property,也意味着响应零碎无奈再_追踪_变动。这段话的意思是,如果咱们的数据应用了Object.freeze,就能够让数据脱离响应式零碎,那么该如何做呢?

比方上面这个表格,因为只是渲染数据,这时候咱们就能够通过Object.freeze来优化性能

<template>  <el-table :data="tableData" >    <el-table-column prop="date" label="日期" width="180" />    <el-table-column prop="name" label="姓名" width="180" />    <el-table-column prop="address" label="地址" />  </el-table></template><script>export default {  data() {    const data = Array(1000)      .fill(1)      .map((item, index) => {        return {          date: '2020-07-11',          name: `子君${index}`,          address: '大西安'        }      })    return {      // 在这里咱们用了Object.freeze      tableData: Object.freeze(data)    }  }}</script>

有的同学可能会有疑难,如果我这个表格的数据是滚动加载的,你这样写我不就没法再给tableData增加数据了吗?是,的确没方法去增加数据了,但还是有方法解决的,比方像上面这样

export default {  data() {    return {      tableData: []    }  },  created() {    setInterval(() => {      const data = Array(1000)        .fill(1)        .map((item, index) => {          // 尽管不能解冻整个数组,然而能够解冻每一项数据          return Object.freeze({            date: '2020-07-11',            name: `子君${index}`,            address: '大西安'          })        })      this.tableData = this.tableData.concat(data)    }, 2000)  }}

正当的应用Object.freeze,是能够节俭不少渲染性能,特地对于IE浏览器,成果还是很显著的,赶快去试试吧。

最初如果你当初须要开发挪动端我的项目,能够理解一下小编整顿的一个开箱即用框架 vue-vant-base,兴许能够帮到你哦

结语

不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。 ——文森特・梵高