这些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, functionmutator(){

    // 将 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更简单

1、使用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来看如何去安装。

1、了解Vue.use的用法

Vue.use是一个全局的方法,它需要在你调用 new Vue() 启动应用之前完成,Vue.use的参数如下

/**

* plugin: 要安装的插件 如 ElementUI

* options: 插件的配置信息 如 {size: 'small'}

*/

Vue.use(plugin, options)

2、模拟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进行插件安装了

3、插件的应用场景

(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" style="width: 100%">

    <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浏览器,效果还是很明显的

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343