Vue - 深入观察对象数组并计算变化?

IT技术 javascript vue.js vuejs2
2021-03-09 04:32:27

我有一个名为的数组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 岁。

我有一个应用程序,我试图在其中观察 people 数组,当任何值发生更改时,请记录更改:

<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 中,结果是一个空数组,而不是我所期望的改变的对象。

如果有人可以提出为什么会发生这种情况或我在这里出错的原因,那么将不胜感激,非常感谢!

6个回答

您的旧值和新值之间的比较函数存在一些问题。最好不要把事情搞得太复杂,因为这会增加你以后的调试工作。你应该保持简单。

最好的方法是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>

我也注意到这一点并有同样的想法,但对象中还包含包含值的值索引,getter 和 setter 在那里,但相比之下,它忽略了它们,因为缺乏更好的理解,我认为它确实如此不对任何原型进行评估。其他答案之一提供了它不起作用的原因,这是因为 newVal 和 oldVal 是同一回事,它有点复杂,但在几个地方已解决,而另一个答案提供了一个体面的解决方法,便于创建用于比较目的的不可变对象。
2021-04-17 04:32:27
顺便说一句,感谢您花时间解释这一点,它帮助我了解了更多关于 Vue 的知识,我很感激!
2021-04-18 04:32:27
这确实是一个可行的解决方案,但并不完全符合我的用例。你看,实际上我有一个应用程序和一个组件,该组件使用 vue-material 表并列出具有在线编辑值的能力的数据。我正在尝试更改其中一个值,然后检查更改的内容,因此在这种情况下,它确实比较了前后数组以查看有什么区别。我可以实施您的解决方案来解决问题吗?事实上,我可能会这样做,但只是觉得它会与 vue-material 中这方面的可用内容背道而驰
2021-04-19 04:32:27
我花了一段时间才理解这一点,但你是绝对正确的,这就像一种魅力,如果你想避免混淆和进一步的问题,这是正确的做事方式:)
2021-04-28 04:32:27
不过,最终,您的方式更容易一目了然,并且在值发生变化时提供了更多可用内容的灵活性。它对我理解在 Vue 中保持简单的好处有很大帮助,但正如你在我的另一个问题中看到的那样,它有点卡住了。非常感谢!:)
2021-05-13 04:32:27

这是定义明确的行为。您无法获得变异对象的旧值那是因为newVal和 都oldVal指向同一个对象。Vue不会保留您改变的对象的旧副本。

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

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

更多关于这里这里的信息

哦,我的帽子,非常感谢!这是一个棘手的问题......我完全希望 val 和 oldVal 不同,但在检查它们之后我看到它是新数组的两个副本,它之前没有跟踪它。多读一点,发现这个关于同样误解的悬而未决的问题:stackoverflow.com/questions/35991494/...
2021-04-23 04:32:27

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

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

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,
    }
  }
})

查看更新的 代码笔

是的,如果您要从原始数组中添加或删除某些内容,只需再次调用该方法,您就可以再次使用该解决方案。
2021-04-16 04:32:27
是的,我也在手表之后通过再次调用该方法来更新它,如果你回到原始值,那么它也会跟踪变化。
2021-04-18 04:32:27
_.cloneDeep() 对我来说真的很有帮助。谢谢!!真的很有帮助!
2021-04-21 04:32:27
哦,我没有注意到隐藏在那里很有意义,并且是一种不太复杂的处理方法(与 github 上的解决方案相反)。
2021-04-26 04:32:27
因此,当它安装时存储数据的副本并使用它来与它进行比较。有趣,但我的用例会更复杂,我不确定在从数组中添加和删除对象时它会如何工作,@Quirk 也提供了很好的链接来解决这个问题。但是我不知道你可以使用vm.$data,谢谢!
2021-05-01 04:32:27

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

new Vue({
    el: "#myElement",
    data:{
        entity: {
            properties: []
        }
    },
    watch:{
        'entity.properties': {
            handler: function (after, before) {
                // Changes detected.    
            },
            deep: true
        }
    }
});
我相信您可能没有理解stackoverflow.com/a/41136186/2110294中描述的 cavet 需要明确的是,这不是问题的解决方案,并且在某些情况下不会像您预期的那样工作。
2021-04-19 04:32:27
这里也一样,正是我需要的!!谢谢。
2021-05-04 04:32:27
这正是我要找的!!!谢啦 ;)
2021-05-07 04:32:27
这正是我要找的!谢谢
2021-05-09 04:32:27

组件方案和深度克隆方案各有优势,但也存在问题:

  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()将接收更改的特定列表项 - 从那里您可以进行任何您喜欢的处理。

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

谢谢埃里克,你提出了有效的观点,如果作为问题的解决方案实施,所提供的方法绝对有用。
2021-05-14 04:32:27