【问题标题】:Vue - Deep watching an array of objects and calculating the change?Vue - 深入观察一组对象并计算变化?
【发布时间】:2017-04-29 08:17:47
【问题描述】:

我有一个名为 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>

我基于 question that I asked yesterday 关于数组比较并选择了最快的工作答案。

所以,此时我希望看到以下结果:{ 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 that I made 中,结果是一个空数组,而不是我所期望的改变的对象。

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

【问题讨论】:

  • 我对以下所有解决方案的唯一问题是它们会在每次更改时触发,在我的情况下,我想进行数据库 API 调用以仅更新更改的项目。输入的 v-model 会触发 100 次,我想在更改发生时记录更改,但是当用户单击应用更改时将它们保存到数据库中,您将如何处理
  • @PirateApp 不确定我是否完全遵循您的问题,但也许这可以解决您遇到的问题:vuejs.org/v2/guide/forms.html#lazy。您还可以选择通过使用选择/更改事件来触发您的逻辑来更深入地控制发生的事情?没有例子很难说,用来代码提出一个新问题并发布一个链接,我会尽力帮助解决它。
  • 这个答案应该会有所帮助。您可以使用计算属性stackoverflow.com/questions/52282586/… 直接查看年龄

标签: javascript vue.js vuejs2


【解决方案1】:

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

最好的方法是创建一个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-material 表并列出具有在线编辑值的能力的数据。我正在尝试更改其中一个值,然后检查更改的内容,因此在这种情况下,它确实比较了前后数组以查看有什么区别。我可以实施您的解决方案来解决问题吗?事实上,我可能会这样做,但只是觉得它会与 vue-material 中这方面可用的内容的流动相悖
  • 顺便说一句,感谢您花时间解释这一点,它帮助我了解了更多关于 Vue 的信息,我非常感激!
  • 我花了一段时间才理解这一点,但你是绝对正确的,这就像一种魅力,如果你想避免混淆和进一步的问题,这是做事的正确方法:)
  • 我也注意到了这一点并且有同样的想法,但对象中还包含包含值的值索引,getter 和 setter 在那里,但相比之下它忽略了它们,因为缺少为了更好地理解,我认为它不会对任何原型进行评估。其他答案之一提供了它不起作用的原因,这是因为 newVal 和 oldVal 是同一件事,它有点复杂,但在一些地方已经解决了,而另一个答案提供了一个体面的工作,以便于创建用于比较目的的不可变对象。
  • 尽管如此,您的方式最终更容易一目了然,并且在值变化时可用的内容方面提供了更大的灵活性。理解在 Vue 中保持简单的好处对我有很大帮助,但正如您在我的另一个问题中看到的那样,它有点卡住了。非常感谢! :)
【解决方案2】:

这是定义明确的行为。您无法获取 mutated 对象的旧值。这是因为newValoldVal 都引用同一个对象。 Vue 将不会保留您变异的对象的旧副本。

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

阅读docs. (vm.$watch) 中的Note 部分

更多关于herehere的信息。

【讨论】:

  • 哦,我的帽子,非常感谢!这是一个棘手的问题......我完全预计 val 和 oldVal 会有所不同,但在检查它们之后我发现它是新数组的两个副本,它之前没有跟踪它。多读一点,发现这个没有答案的 S.O.关于同样误解的问题:stackoverflow.com/questions/35991494/…
【解决方案3】:

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

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

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

查看更新codepen

【讨论】:

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

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

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

【讨论】:

  • 我相信您可能会错过对stackoverflow.com/a/41136186/2110294 中描述的警告的理解。需要明确的是,这不是问题的解决方案,并且在某些情况下不会像您预期的那样起作用。
  • 这正是我所期待的!。谢谢
  • 这里一样,正是我需要的!!谢谢。
  • 这正是我想要的!!!谢谢人;)
【解决方案5】:

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

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

我还记录了一个more complex scenario here,以防您在列表中添加/删除项目(而不是仅仅操作已经存在的项目)。

【讨论】:

  • 感谢 Erik,您提出了有效的观点,如果作为问题的解决方案实施,所提供的方法绝对有用。
【解决方案6】:

如果我们有对象或对象数组,并且我们想在 Vuejs 或 NUXTJS 中观看它们,则需要在观看中使用deep: true

    watch: {
      'Object.key': {
         handler (val) {
             console.log(val)
         },
         deep: true
       } 
     }

     watch: {
      array: {
         handler (val) {
             console.log(val)
         },
         deep: true
       } 
     }

【讨论】:

    猜你喜欢
    • 2019-01-08
    • 2018-07-12
    • 2021-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-19
    • 2019-02-18
    • 2018-01-15
    相关资源
    最近更新 更多