在開發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方法來實現的,但透過這個方法只能監聽到已有屬性,新增的屬性是無法監聽到的,但我就是想監聽,小編你說咋辦吧。以下小編提供了四種方式,如果有更多方式,歡迎下方留言區告訴我。
例如上面的公眾號,我可以提前在userInfo裡面定義好,這樣就不是新增屬性了,就像下面這樣
data() { return { userInfo: { name: '子君', sex: '男', // 我先提前定义好 officialAccount: '' } } }
雖然無法為userInfo裡面新增新的屬性,但是因為userInfo已經定義好了,所以我直接修改userInfo的值不就可以了麼,所以也可以像下面這樣寫
this.userInfo = { // 将原来的userInfo 通过扩展运算法复制到新的对象里面 ...this.userInfo, // 添加新属性 officialAccount: '前端有的玩' }
其實上面兩種方法都有點取巧的嫌疑,其實對於新增屬性,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,其他時候一般不會需要使用。
#我覺得$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不能偵測到以下變動的陣列:
所以在上例中通过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对数组的以下方法进行的包装变异:
所以在操作数组的时候,调用上面这些方法是可以保证数据可以正常响应,下面是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 });
我想把时间戳显示成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>
通过上面的修改是不是就简单多了
有些过滤器使用的很频繁,比如上面提到的日期过滤器,在很多地方都要使用,这时候如果在每一个要用到的组件里面都去定义一遍,就显得有些多余了,这时候就可以考虑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是一个全局的方法,它需要在你调用 new Vue() 启动应用之前完成,Vue.use的参数如下
/** * plugin: 要安装的插件 如 ElementUI * options: 插件的配置信息 如 {size: 'small'} */ Vue.use(plugin, options)
想一下,使用Vue.use(ElementUI,{size: 'small'}) 之后我们可以用到哪些element-ui提供的东西
可以直接在组件里面用element-ui的组件,不需要再import
可以直接使用v-loading指令
通过this.$loading在组件里面显示loading
其他...
// 这个是一个按钮组件 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进行插件安装了。大家可能会有疑问,为什么我要用这种写法,不用这种写法我照样可以实现功能啊。小编认为这种写法有两个优势
标准化,通过提供一种统一的开发模式,无论对插件开发者还是使用者来说,都有一个规范去遵循。
插件缓存,Vue.use 在安装插件的时候,会对插件进行缓存,即一个插件如果安装多次,实际上只会在第一次安装时生效。
添加全局方法或者 property。
添加全局资源:指令/过滤器/过渡等。
通过全局混入来添加一些组件选项。
添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如element-ui
当一个 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,也许可以帮到你哦
不要吹灭你的灵感和你的想象力; 不要成为你的模型的奴隶。 ——文森特?梵高
相关推荐:
更多编程相关知识,请访问:编程入门!!
以上是分享Vue專案中會用到的一些實戰技巧點的詳細內容。更多資訊請關注PHP中文網其他相關文章!