vue.js - 更新数组内的对象不会触发更新
问题描述
在我的根 Vue 实例中,我有一个包含一些数据的对象数组,我用它来渲染一组组件。这些组件对提供给它们的数据对象有一个观察者,它应该在每次更新对象时进行异步调用。
问题是,当我更新数组中一个对象的属性时,不会调用观察者。它不应该落入 Vue 的任何警告中,因为 a)我没有添加新属性,只是更新现有属性,并且 b)我没有以任何方式改变数组本身。那么为什么会这样呢?我该如何解决?
我的主要 Vue 实例:
let content = new Vue({
el: '#content',
data: {
testData: [
{ name: 'test1', params: {
testParam: 1
} },
{ name: 'test2', params: {
testParam: 1
} },
{ name: 'test3', params: {
testParam: 1
} }
]
}
});
我用来渲染组件的代码:
<div id="content">
<div v-for="item in testData">
<test-component v-bind="item"></test-component>
</div>
</div>
我的组件:
Vue.component('test-component', {
props: {
name: {
type: String,
required: true
},
params: {
type: Object,
required: true
}
},
data: function() {
return { asyncResult: 0 };
},
watch: {
params: function(newParams, oldParams) {
// I use a custom function to compare objects, but that's not the issue since it isn't even being called.
console.log(newParams);
if(!this.compareObjs(newParams, oldParams)) {
// My async call, which mutates asyncResult
}
}
},
template: `
<span>{{ asyncResult }}</span>
`
});
我的目标是改变params
给定对象的属性并触发观察者重新渲染相应的组件,但是当我尝试直接改变它时它不起作用。
示例(以及我希望我的组件工作的方式):
content.testData[2].params.testParam = 5;
不幸的是,事实并非如此。使用Vue.set
也不起作用:
Vue.set(content.testData[2].params, 'testParam', 5);
我发现唯一有效的方法是完全分配一个新对象(这不是我每次必须改变属性时都想做的事情):
content.testData[2].params = Object.assign({}, content.testData[2].params, { testParam: 5 });
正如在类似问题中所建议的那样,我还尝试使用深度观察器,但在我的情况下它不起作用。当我使用深度观察器时,会调用该函数,但无论我为我的属性设置哪个值,两者newParams
和始终是同一个对象。oldParams
是否有解决方案允许我仅通过设置属性来改变数组项?那将是最理想的结果。
解决方案
第一件事。
使用Vue.set
不会有帮助。Vue.set
用于设置 Vue 的响应式系统无法跟踪的属性的值。这包括按索引更新数组或向对象添加新属性,但这些都不适用。您正在更新反应对象的现有属性,因此 usingVue.set
除了使用=
.
下一个...
Vue 在将对象作为道具传递时不会复制它们。如果您将对象作为道具传递,则子组件将获得与父组件相同的对象的引用。如果deep
您更新该对象中的属性但它仍然是同一个对象,则会触发观察者。传递给观察者的旧值和新值将是同一个对象。这在文档中有所说明:
https://vuejs.org/v2/api/#vm-watch
注意:当改变(而不是替换)对象或数组时,旧值将与新值相同,因为它们引用相同的对象/数组。Vue 不保留 pre-mutate 值的副本。
正如您所注意到的,一种解决方案是在执行更新时使用全新的对象。最终,如果您想比较新旧对象,那么您别无选择,只能在某处复制该对象。变异时复制是一个完全有效的选择,但这不是唯一的选择。
另一种选择是使用计算属性来创建副本:
new Vue({
el: '#app',
data () {
return {
params: {
name: 'Lisa',
id: 5,
age: 27
}
}
},
computed: {
watchableParams () {
return {...this.params}
}
},
watch: {
watchableParams (newParams, oldParams) {
console.log(newParams, oldParams)
}
}
})
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>
<div id="app">
<input v-model="params.name">
<input v-model="params.id">
<input v-model="params.age">
</div>
对此有几点说明:
- 此示例中的计算属性仅创建一个浅拷贝。如果你需要一个深拷贝,它会更复杂,比如
JSON.stringify
/JSON.parse
可能是一个选项。 - 计算属性实际上不必复制所有内容。如果您只想查看属性的子集,则只需复制这些属性。
watch
不需要是deep
。计算属性将创建对其使用的属性的依赖关系,如果其中任何一个发生更改,它将被重新计算,每次都创建一个新对象。我们只需要观察那个物体。- Vue 缓存计算属性的值。当依赖项更改时,旧值被标记为过时但不会立即丢弃,以便可以将其传递给观察者。
这种方法的主要优点是处理复制的地方。进行变异的代码不需要担心它,复制是由需要复制的同一组件执行的。
推荐阅读
- excel - 复制并粘贴到名称不等于列表中值的工作表?
- python - CSV 文件已写入,但输出不正确
- http - 无法成功实现 http.Handler
- mysql - Cannot connect from Sequalize.js, can connect from MySQL client
- assembly - 组装:嵌套循环的问题
- c++ - 无法在 main 中调用 struct 函数
- python - 来自 PIL 的图像中的 PermissionError
- c++ - 编译与 libmpfr.so 相关的 GCC 8.2.0 时出现链接器错误
- php - 如何在 Codeigniter 中使用 AJAX 和 PHP 从 MySQL 数据库中删除记录
- delphi - FireMonkey 一个应用程序中的不同画布类