如有错误望请指出
在开发过程中可能会遇到修改了值,视图未发生更新的情况。
实际上在vue的官方文档中也有描述,见 深入响应式原理
这里会以实际案例讲述
问题重现
下面是一个循环显示对象的vuejs代码(建议有条件的可以直接运行查看效果)
<!DOCTYPE html>
<html lang="en">
<head>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
<div id="root">
<div v-for="(v,k) in obj">{{k}} => {{v}}</div>
<button @click="setA">赋值A</button>
<button @click="setB">赋值b</button>
<button @click="print">打印</button>
</div>
</body>
<script>
new Vue({
el: '#root',
data() {
return {
obj: {
a: 1
}
}
},
methods: {
setA() {
this.obj.a = 'new A';
},
setB() {
this.obj.b = 2;
},
print() {
console.log(this.obj)
}
}
});
</script>
</html>
通过浏览器打开后初始状态如下图:
点击赋值A
后,页面发生了预期的变化,结果如下:
点击 赋值b
后,此时发现并没有发生预期的变化,html并没有渲染出b => 2
这样的内容。
但实际对象确实新增了一个值为2
的b
属性,点击打印
验证
如何解决?
方法一:提前定义
咱们可以很明显的发现差异,a
是原来就定义在obj
中的,而b
不是。
因此第一个方法就是定义一个空值b
。如下:
...
...
data() {
return {
obj: {
a: 1,
b: null
}
}
}
...
...
方法二:使用 $set
方法一
虽然有效,但有时确实存在无法预先知道要新增的属性名或者说懒得(?)。
出现问题的本质是vue不知道新增了一个b
属性,vue本身提供了一个$set方法用于解决这个问题,将setB
中赋值方法修改。
代码如下:
setB() {
// this.obj.b = 2;
this.$set(this.obj, 'b', 2);
},
此时再点击赋值b
就可以看到html发生了符合预期的变化。
为什么?
要实现在值发生变化时页面也同时发生改变,关键是在于发现值的变化。
Vue利用了Object.defineProperty
(这里有更详细的描述)。简单的说,使用这个方法可以给对象的某一个属性加上getter/setter
方法,之后在读取这个值时会执行getter
方法,在设置时会执行setter
方法。以此就可以监听到对象属性的存取操作,继而也就能实现html的更新。
因此,当在data
中定义完值并返回后,vue会对其中对象(数组的情况暂不考虑)
的所有属性,进行递归式的Object.defineProperty
。
这也是为什么,需要提前定义。因为这个方法并不能监听到对象属性的新增
与删除
。
vue作者因此提供了$set
与$delete
这样的显式操作。以支持响应式。
另外对于数组
对于数组,可以知道能够对数组自身产生变化的方法只有七个。
分别是:push
,pop
,shift
,unshift
,splice
,sort
,reverse
因此vue中对这些方法重新进行了定义,能够在调用这些方法时发现数组的变化。
所以!当以数组下标的方式修改数组时,vue也是发现不了的,这个时候还是得使用$set
来解决。
其他:Proxy
在vue3中Object.defineProperty
方式被放弃,转而使用Proxy
实现对数据的观察。Proxy
从能力上是优于Object.defineProperty
的,更详细内容的,先自己查查吧。。