Vue-深入观察对象阵列并计算变化?


108

我有一个称为people包含对象的数组,如下所示:

之前

[
  {id: 0, name: 'Bob', age: 27},
  {id: 1, name: 'Frank', age: 32},
  {id: 2, name: 'Joe', age: 38}
]

它可以更改:

[
  {id: 0, name: 'Bob', age: 27},
  {id: 1, name: 'Frank', age: 33},
  {id: 2, name: 'Joe', age: 38}
]

请注意,弗兰克刚满33岁。

我有一个应用程序,我试图在其中查看人员数组,并且当任何值更改时,然后记录更改:

<style>
input {
  display: block;
}
</style>

<div id="app">
  <input type="text" v-for="(person, index) in people" v-model="people[index].age" />
</div>

<script>
new Vue({
  el: '#app',
  data: {
    people: [
      {id: 0, name: 'Bob', age: 27},
      {id: 1, name: 'Frank', age: 32},
      {id: 2, name: 'Joe', age: 38}
    ]
  },
  watch: {
    people: {
      handler: function (val, oldVal) {
        // Return the object that changed
        var changed = val.filter( function( p, idx ) {
          return Object.keys(p).some( function( prop ) {
            return p[prop] !== oldVal[idx][prop];
          })
        })
        // Log it
        console.log(changed)
      },
      deep: true
    }
  }
})
</script>

我基于昨天问过的关于数组比较的问题,选择了最快的工作答案。

因此,目前我希望看到以下结果: { id: 1, name: 'Frank', age: 33 }

但是我在控制台中得到的只是(记住我在组件中拥有它):

[Vue warn]: Error in watcher "people" 
(found in anonymous component - use the "name" option for better debugging messages.)

我制作Codepen中,结果是一个空数组,而不是更改后的对象,这正是我所期望的。

如果有人能说出发生这种情况的原因或我在这里出了错,那么我们将不胜感激,非常感谢!

Answers:


136

您在旧值和新值之间的比较功能出现问题。最好不要使事情复杂化,因为这会增加以后的调试工作。您应该保持简单。

最好的方法是创建一个,person-component并在每个组件的内部分别监视每个人,如下所示:

<person-component :person="person" v-for="person in people"></person-component>

请在下面找到一个观看内部人物组件的工作示例。如果您想在父方处理该$emit事件,则可以向上发送一个事件,其中包含已id修改人员的。

Vue.component('person-component', {
    props: ["person"],
    template: `
        <div class="person">
            {{person.name}}
            <input type='text' v-model='person.age'/>
        </div>`,
    watch: {
        person: {
            handler: function(newValue) {
                console.log("Person with ID:" + newValue.id + " modified")
                console.log("New age: " + newValue.age)
            },
            deep: true
        }
    }
});

new Vue({
    el: '#app',
    data: {
        people: [
          {id: 0, name: 'Bob', age: 27},
          {id: 1, name: 'Frank', age: 32},
          {id: 2, name: 'Joe', age: 38}
        ]
    }
});
<script src="https://unpkg.com/vue@2.1.5/dist/vue.js"></script>
<body>
    <div id="app">
        <p>List of people:</p>
        <person-component :person="person" v-for="person in people"></person-component>
    </div>
</body>


那确实是一个可行的解决方案,但这并不完全符合我的用例。您会看到,实际上我有一个应用程序和一个组件,该组件使用Vue材质表并列出数据并可以在线编辑值。我试图更改值之一,然后检查更改的内容,因此,在这种情况下,它确实比较了数组之前和之后,以查看有什么区别。我可以实施您的解决方案来解决问题吗?确实,我可能会这样做,但只是觉得这将不利于在Vue材料中提供这方面的可用资源
Craig van Tonder,2016年

2
顺便说一句,感谢您抽出宝贵的时间来解释这一点,它帮助我了解了有关Vue的更多信息,对此我深表感谢!
Craig van Tonder

我花了一段时间来理解这一点,但是您是完全正确的,这就像一种魅力,如果您想避免造成混乱和其他问题,这是正确的处理方法:)
Craig van Tonder

1
我也确实注意到了这一点,并且有相同的想法,但是对象中还包含值索引,其中包含值,getter和setter在那里,但由于缺乏更好的理解,它忽略了它们,我认为它确实没有评估任何原型。另一个答案提供了它不起作用的原因,这是因为newVal和oldVal是同一件事,虽然有点复杂,但是在某些地方已经解决过,但是另一个答案提供了不错的解决方法,可以轻松创建用于比较目的的不可变对象。
Craig van Tonder

1
最终,您的方法一目了然,并且在价值变化时可以使用的方式方面更具灵活性。它帮助我了解了在Vue中保持简单性的好处,但是您在我的另一个问题中看到了一点点的坚持。非常感谢!:)
Craig van Tonder

21

我已经更改了它的实现以解决您的问题,我创建了一个对象来跟踪旧的更改并将其与之进行比较。您可以使用它来解决您的问题。

在这里,我创建了一个方法,其中旧值将存储在单独的变量中,然后将其用于手表。

new Vue({
  methods: {
    setValue: function() {
      this.$data.oldPeople = _.cloneDeep(this.$data.people);
    },
  },
  mounted() {
    this.setValue();
  },
  el: '#app',
  data: {
    people: [
      {id: 0, name: 'Bob', age: 27},
      {id: 1, name: 'Frank', age: 32},
      {id: 2, name: 'Joe', age: 38}
    ],
    oldPeople: []
  },
  watch: {
    people: {
      handler: function (after, before) {
        // Return the object that changed
        var vm = this;
        let changed = after.filter( function( p, idx ) {
          return Object.keys(p).some( function( prop ) {
            return p[prop] !== vm.$data.oldPeople[idx][prop];
          })
        })
        // Log it
        vm.setValue();
        console.log(changed)
      },
      deep: true,
    }
  }
})

查看更新的 代码笔


因此,在挂载时,存储数据的副本,并使用该副本与之进行比较。有趣,但是我的用例会更复杂,而且我不确定在从数组中添加和删除对象时这将如何工作,@ Quirk也提供了很好的链接来解决此问题。但我不知道您可以使用vm.$data,谢谢!
Craig van Tonder

是的,我还要在手表之后通过再次调用该方法来更新它,如果您返回到原始值,它也会跟踪更改。
Viplock '16

哦,我没有注意到隐藏在那里很有意义,并且是一种较简单的处理方式(与github上的解决方案相对)。
Craig van Tonder

ya,如果要在原始数组中添加或删除某些东西,只需再次调用该方法,您就可以再次使用该解决方案了。
Viplock '16

1
_.cloneDeep()在我的情况下确实有所帮助。谢谢!!真的很有帮助!
克里斯蒂安娜·佩雷拉

18

这是定义明确的行为。您无法获取突变对象的旧值。这是因为newVal和都oldVal引用相同的对象。Vue 不会保留您突变的对象的旧副本。

如果用另一个替换了该对象,Vue会为您提供正确的引用。

阅读文档中Note部分。(vm.$watch

这里这里的更多信息


3
哦,我的帽子,非常感谢!这是一个棘手的问题……我完全希望val和oldVal是不同的,但是在检查它们之后,我看到它是新数组的两个副本,之前没有跟踪它。了解多一点,发现关于同一误解这个悬而未决太问题:stackoverflow.com/questions/35991494/...
克雷格面包车托德

5

这就是我用来深入观察对象的方法。我的要求是观察对象的子字段。

new Vue({
    el: "#myElement",
    data:{
        entity: {
            properties: []
        }
    },
    watch:{
        'entity.properties': {
            handler: function (after, before) {
                // Changes detected.    
            },
            deep: true
        }
    }
});

我相信您可能会缺少在stackoverflow.com/a/41136186/2110294中描述的对Cavet的理解。只是要清楚一点,这不是解决问题的方法,在某些情况下不会像您期望的那样起作用。
Craig van Tonder

这正是我一直在寻找的东西!谢谢
Jaydeep Shil

同样在这里,正是我所需要的!谢谢。
高达

4

组件解决方案和深层克隆解决方案虽然具有优势,但也存在一些问题:

  1. 有时您想跟踪抽象数据中的更改-在该数据周围构建组件并非总是有意义。

  2. 每次进行更改时,深克隆整个数据结构可能会非常昂贵。

我认为有更好的方法。如果要观看列表中的所有项目并知道列表中的哪个项目已更改,则可以分别在每个项目上设置自定义观察程序,如下所示:

var vm = new Vue({
  data: {
    list: [
      {name: 'obj1 to watch'},
      {name: 'obj2 to watch'},
    ],
  },
  methods: {
    handleChange (newVal) {
      // Handle changes here!
      console.log(newVal);
    },
  },
  created () {
    this.list.forEach((val) => {
      this.$watch(() => val, this.handleChange, {deep: true});
    });
  },
});

有了这种结构,您handleChange()将收到已更改的特定列表项-从那里您可以进行所需的任何处理。

我也记录了 更复杂的场景,以防您向列表中添加/删除项目(而不是仅操作已经存在的项目)。


感谢Erik,您提出了有效的观点,如果将其实施为问题的解决方案,那么所提供的方法绝对是最有用的。
Craig van Tonder
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.