乐趣区

关于前端:让Vue项目更丝滑的几个小技巧

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

在开发 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.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, 兴许能够帮到你哦

结语

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

退出移动版