转发链接:
前言
一阵按键声传来,隐隐约约地试探着话语。 产品不稳定,今晚没人退货。
在开发Vue的过程中,我们经常会遇到这样或那样的一些问题,然后不得不卡住很长一段时间。 问题解决后,我们发现原来的一些细节还没有把握好。 明天小编就整理一下项目中会用到的一些实用方法,希望能够帮助到正在为赚钱而苦苦挣扎的你。
数据无反应,可能使用有问题
前几天有同学给我发了一段代码,然后说vue有bug。 明明写的没有问题,为什么数据没有反应,一定是Vue的bug? 我以为他比尤玉溪强,我也承受不起,所以就没有理他。 而确实有时候我们在开发过程中会遇到数据无响应的情况,那么我们该怎么办呢? 例如下面这段代码:
用户名: {{ userInfo.name }}
用户性别: {{ userInfo.sex }}
公众号: {{ userInfo.officialAccount }}
export default {
data() {
return {
userInfo: {
name: '子君',
sex: '男'
}
}
},
methods: {
// 在这里添加用户的公众号
handleAddOfficialAccount() {
this.userInfo.officialAccount = '前端有的玩'
}
}
}
前面的代码中,我们想要给用户信息添加公众号属性,添加this.userInfo.officialAccount='Backend Some Play'后,并没有生效。 为什么?
这是因为在Vue内部,数据响应是通过使用Object.definePrototype来监听对象各个key的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 的存在让很多后端开发者无法关注单向数据绑定的原理,因为无论什么时候,我更改数据后,调用 $forceUpdate 都会让 Vue 组件重新渲染,错误不存在。 而事实上,这种方法并不推荐,因为它会造成很多不必要的性能消耗。
链表的具体形式
虽然不仅仅是对象,链表也有数据改变不响应的情况,比如下面的代码
-
{{ item }}
export default {
data() {
return {
list: ['张三', '李四']
}
},
methods: {
// 修改用户名称
handleChangeName() {
this.list[0] = '王五'
}
}
}
里面的代码想要把张三的名字改成王五,但是这个改变并没有生效,因为Vue无法测量以下改变的字段:
当直接借助index设置一个item时,如:this.list[index]=newValue 改变字段的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 对链表的包装器突变有以下几种方式:
因此,在操作字段时,调用该方法可以保证数据能够正常响应。 以下是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-DDHH:mm:ss的格式显示时间戳怎么办? 在将日期渲染到模板之前,是否需要先在代码中格式化日期?如下所示
{{ dateStr }}
-
{{ item.date }}
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')
}
})
}
}
}
和之前的写法一样,需要对每个日期数组调用format,然后通过估计属性进行转换?这时候可以考虑使用Vue提供的过滤器来简化
{{ dateStr | formatDate }}
-
{{ item.date | formatDate }}
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()
}
]
}
}
}
和之前的改动相比是不是简单多了?
注册全局过滤器
有些过滤器经常使用。 比如上面提到的日期过滤器,很多地方都会用到。 这时候如果你在每个要使用的组件上都定义它,就显得有点多余了。 这时候Hou可以考虑Vue.filter来注册全局过滤器
对于全局过滤器,通常建议在项目上方添加filters目录,然后添加到filters目录下
// filtersindex.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中,就可以直接在组件上使用了。 例如上面的代码可以改为
{{ dateStr | formatDate }}
-
{{ item.date | formatDate }}
export default {
data() {
return {
date: Date.now(),
list: [
{
date: Date.now()
}
]
}
}
}
是不是更容易
开发了一个插件库,我们来安装一下
在使用一些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可以直接在组件上使用,太神奇了(yuck)。 接下来我们来实现一个简化版的element,看看如何安装。
了解Vue.use的用法
Vue.use 是一个全局方法。 它需要在调用 newVue() 启动应用程序之前完成。 Vue.use的参数如下
/**
* plugin: 要安装的插件 如 ElementUI
* options: 插件的配置信息 如 {size: 'small'}
*/
Vue.use(plugin, options)
模拟element-ui的安装逻辑
想一想,使用 Vue.use(ElementUI,{size:'small'}) 然后我们就可以使用 element-ui 提供的东西
可以直接在组件上使用element-ui组件,不需要导入,可以直接使用v-loading命令,通过this.$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 实例添加到 Vue.prototype 来添加 Vue 实例的方法。 提供自己的 API 并提供其中提到的一个或多个函数的库。 如果 element-ui 提高了 Vue 渲染性能,请了解 Object.freeze
当创建 Vue 实例时,它将数据对象中的所有属性添加到 Vue 的反应系统中。 当这个属性的值发生变化时,视图会形成“响应”,即匹配更新为新值。 而这个过程实际上是消耗性能的压缩elementui插件,所以对于一些数据较多但只是显示的界面,不需要在响应式系统中添加属性,这样可以增强渲染性能。 怎么做压缩elementui插件,你需要了解Click on Object。 冻结。
在Vue官网上,有这样一段话:这里唯一的例外是使用Object.freeze(),这将阻止更改现有属性,也意味着响应系统无法再跟踪更改。 这段话的意思是,如果我们的数据使用Object.freeze,就可以将数据与响应式系统分离,那么我们该怎么办呢?
比如下表,由于只是渲染数据,此时我们可以使用Object.freeze来优化性能
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)
}
}
}
有的朋友可能会有疑问,如果我表中的数据是通过滚动加载的,那么这样写我是不是就无法向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,或许会对你有所帮助。
结语
不要耗尽你的灵感和想象力; 不要成为模型的奴隶。 - 文森特 - 梵高
发表评论