让Vue项目更丝滑的几个小技巧

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

在开发Vue的过程当中,咱们常常会遇到一些这样那样的问题,而后要卡好半天,等问题解决了才发现原来一些细节知识点仍是没有掌握好。今天小编就整理了几个在项目中会用到的一些实战技巧点,但愿能够帮助到正在努力赚钱的你。江湖规矩,先赞后看,艳遇不断。css

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

前几天有朋友给我发了一段代码,而后说Vuebug,他明明写的没问题,为啥数据就不响应呢,必定是Vuebug?我感受他比尤雨溪要牛逼,高攀不起,就没有理他了。可是确实有时候咱们在开发时候会遇到数据不响应的状况,那怎么办呢?好比下面这段代码:前端

<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

这是由于在Vue内部,数据响应是经过使用Object.definePrototype监听对象的每个键的getter,setter方法来实现的,但经过这种方法只能监听到已有属性,新增的属性是没法监听到的,但我就是想监听,小编你说咋办吧。下面小编提供了四种方式,若是有更多方式,欢迎下方评论区告诉我。git

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

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

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

2. 直接替换掉userInfo

虽然没法给userInfo里面添加新的属性,可是由于userInfo已经定义好了,因此我直接修改userInfo的值不就能够了么,因此也能够像下面这样写算法

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

3. 使用Vue.set

其实上面两种方法都有点取巧的嫌疑,其实对于新增属性,Vue官方专门提供了一个新的方法Vue.set用来解决新增属性没法触发数据响应。element-ui

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仅仅依赖gettersetter,是没法作到在数组调用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.js

import 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,也许能够帮到你哦

结语

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

欢迎你们关注个人公众号【前端有的玩】,咱们一块儿玩前端~

相关文章
相关标签/搜索