【问题标题】:Updates to object inside array do not trigger updates更新数组内的对象不会触发更新
【发布时间】:2019-09-06 17:10:17
【问题描述】:

在我的根 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 });

我也尝试过使用深度观察器,正如在类似问题中所建议的那样,但在我的情况下它不起作用。当我使用深度观察器时,会调用函数 ,但 newParamsoldParams 始终是同一个对象,无论我为我的属性设置哪个值。

是否有解决方案允许我仅通过设置属性来改变数组项?这将是最理想的结果。

【问题讨论】:

  • 请参阅 Vue 文档中的 Common gotchas,其中解释了为什么会发生这种情况以及如何避免它。如果你考虑一下,Vue 不可能知道数组中对象的属性,这太复杂了。每次都创建一个新对象将是非常简单的解决方案,尽管不是最有效的。

标签: vue.js


【解决方案1】:

首先要做的事情。

使用Vue.set 不会有帮助。 Vue.set 用于设置 Vue 的响应式系统无法跟踪的属性值。这包括按索引更新数组或向对象添加新属性,但这些都不适用。您正在更新反应对象的现有属性,因此使用 Vue.set 只会使用 = 设置它。

下一步...

Vue 在将对象作为道具传递时不会复制它们。如果您将对象作为道具传递,则子组件将获得与父组件相同的对象的引用。如果您更新该对象中的属性但它仍然是同一个对象,则会触发deep 观察程序。传递给观察者的旧值和新值将是同一个对象。这在文档中有所说明:

https://vuejs.org/v2/api/#vm-watch

注意:当改变(而不是替换)对象或数组时,旧值将与新值相同,因为它们引用相同的对象/数组。 Vue 不保留预突变值的副本。

如您所见,一种解决方案是在执行更新时使用全新的对象。最后,如果您想比较新旧对象,那么您别无选择,只能在某处复制对象。变异时复制是一个完全有效的选择,但这不是唯一的选择。

另一种选择是使用计算属性来创建副本:

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>

对此有几点说明:

  1. 此示例中的计算属性仅创建一个浅拷贝。如果你需要一个深拷贝,它会更复杂,像JSON.stringify/JSON.parse 这样的选项可能是一种选择。
  2. 计算属性实际上不必复制所有内容。如果您只想查看属性的子集,则只需复制这些属性。
  3. watch 不必是deep。计算属性将创建对其使用的属性的依赖关系,如果其中任何一个发生更改,它将被重新计算,每次都创建一个新对象。我们只需要观察那个物体。
  4. Vue 缓存计算属性的值。当依赖项发生更改时,旧值会被标记为过时,但不会立即丢弃,以便将其传递给观察者。

这种方法的主要优势在于处理复制的位置。进行变异的代码无需担心,复制由需要复制的同一组件执行。

【讨论】:

    【解决方案2】:

    如您所说,您需要在watch 中使用deep 属性。

    使用Vue.set,您应该重新安装数组中的整个对象,例如:

    const newObj = {
      name: 'test1',
      params: {
        testParam: 1,
      },
    };
    
    Vue.set(yourArray, newObj, yourIndex);
    

    请注意,您在数组中设置了一些值,在这种情况下,数组包含对象。

    【讨论】:

      猜你喜欢
      • 2020-08-12
      • 2019-01-28
      • 1970-01-01
      • 2021-11-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-07-14
      • 2020-09-17
      相关资源
      最近更新 更多