【做棵大树】[Vue.js] v-model 绑定对象不实时更新

本文转载自「做棵大树[Vue.js] v-model 绑定对象不实时更新!

在最近参与的一个项目中,前端用到了 vue.js 框架,期间有个功能需要动态的向一个被绑定的对象中添加属性。但是在实际应用中问题出现了:在向对象中添加属性后,与对象绑定的组件内容却未发生变化,必须要再次刷新组件,其内容才会变为更改后的内容。

起初我以为是属性没有添加成功,因为在我的印象中 v-model 是双向绑定的,不会出现不更新的状态。在我查看 Devtools 中的监控后,发现对应的对象确实添加了指定的属性。

于是,我前去查看了官方文档,找到了官方给出的解释:Vue.js 如何追踪变化

官方解释

当你把一个普通的 JavaScript 对象传入 Vue 实例作为 data 选项,Vue 将遍历此对象所有的 property,并使用 Object.defineProperty 把这些 property 全部转为 getter/setterObject.defineProperty 是 ES5 中一个无法 shim 的特性,这也就是 Vue 不支持 IE8 以及更低版本浏览器的原因。

每个组件实例都对应一个 watcher 实例,它会在组件渲染的过程中把“接触”过的数据 property 记录为依赖。之后当依赖项的 setter 触发时,会通知 watcher,从而使它关联的组件重新渲染。

[Vue.js] v-model 绑定对象不实时更新

检测变化的注意事项
由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。尽管如此我们还是有一些办法来回避这些限制并保证它们的响应性。

对于对象

Vue 无法检测 property 的添加或移除。由于 Vue 会在初始化实例时对 property 执行 getter/setter 转化,所以 property 必须在 data 对象上存在才能让 Vue 将它转换为响应式的。例如:

1
2
3
4
5
6
7
8
var vm = new Vue({
data:{
a:1
}
})
// 'vm.a' 是响应式的
vm.b = 2
// 'vm.b' 是非响应式的

对于已经创建的实例,Vue 不允许动态添加根级别的响应式 property。但是,可以使用 Vue.set(object, propertyName, value) 方法向嵌套对象添加响应式 property。例如,对于:

1
Vue.set(vm.someObject, 'b', 2)

您还可以使用 vm.$set 实例方法,这也是全局 Vue.set 方法的别名:

1
this.$set(this.someObject,'b',2)

有时你可能需要为已有对象赋值多个新 property,比如使用 Object.assign()_.extend()。但是,这样添加到对象上的新 property 不会触发更新。在这种情况下,你应该用原对象与要混合进去的对象的 property 一起创建一个新的对象。

1
2
// 代替 'Object.assign(this.someObject, { a: 1, b: 2 })'
this.someObject = Object.assign({}, this.someObject, { a: 1, b: 2 })

这是对于对象赋值的解决方式,在采用了官方的解决方案 this.$set(object, key, value) 后确实实现了实时更新的效果。同时对于数组等情况,可查看 余下官方文档

为什么会这样呢?

如官方所说 *“由于 JavaScript 的限制,Vue 不能检测数组和对象的变化。”*,但是为什么会这样呢?

借用 Segmentfault UKer 的回答:

ECMAScript 中有两种属性:数据属性访问器属性;

数据属性的描述符为:Configurable,Enumerable,Writable,Value;

访问器属性的描述符为:Configurable, Enumerable,set,get。

当我们使用new Vue(obj),其内部发生了大体如下代码的转换,即,将数据属性转换为了访问器属性

1
2
3
4
5
6
7
8
9
10
11
12
function Vue(obj){
obj.data.keys().forEach((prop, index) => {
Object.defineProperty(obj.data, prop, {
set(){
//可以在此处进行事件监听
},
get(){
}
})
})
return obj;
}

但是当我们后面再次使用普通的赋值,仅仅是赋值了一个数据属性的,这个属性是不会具有访问器属性的事件监听功能的。

至此,v-model 绑定数据不实时更新的问题方才得到了解决。

参考资料: Vue的使用v-model不实时更新问题的解决

[Vue.js] v-model 绑定对象不实时更新