web-dev-qa-db-fra.com

Vue - Regarder en profondeur un tableau d'objets et calculer le changement?

J'ai un tableau appelé people qui contient les objets suivants:

Avant

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

Cela peut changer:

Après

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

Remarquez Frank vient d'avoir 33 ans.

J'ai une application où j'essaie de regarder le tableau des personnes et lorsque l'une des valeurs change, enregistrez le changement:

<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>

Je me suis basé sur la question que j’ai posée hier sur les comparaisons de tableaux et sélectionné la réponse la plus rapide.

Donc, à ce stade, je m'attends à voir un résultat de: { id: 1, name: 'Frank', age: 33 }

Mais tout ce que je reçois dans la console est (gardant à l’esprit que je l’avais dans un composant):

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

Et dans le codepen que j'ai créé , le résultat est un tableau vide et non l'objet modifié qui a changé, ce qui correspond à ce à quoi je m'attendais.

Si quelqu'un pouvait suggérer pourquoi cela se produit ou si je me suis trompé, ce serait grandement apprécié, merci beaucoup!

79
Craig van Tonder

Votre fonction de comparaison entre l'ancienne valeur et la nouvelle valeur a un problème. Il vaut mieux ne pas trop compliquer les choses, car cela augmentera votre effort de débogage plus tard. Vous devriez garder les choses simples.

Le meilleur moyen est de créer un person-component et de regarder chaque personne séparément dans son propre composant, comme indiqué ci-dessous:

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

Veuillez trouver ci-dessous un exemple de travail pour regarder un composant de personne à l'intérieur. Si vous voulez le gérer côté parent, vous pouvez utiliser $emit pour envoyer un événement vers le haut, contenant le id de la personne modifiée.

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/[email protected]/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>
102
Mani

J'ai changé son implémentation pour résoudre votre problème, j'ai créé un objet pour suivre les anciens changements et les comparer à ceux-là. Vous pouvez l'utiliser pour résoudre votre problème.

Ici, j'ai créé une méthode, dans laquelle l'ancienne valeur sera stockée dans une variable séparée et, qui sera ensuite utilisée dans une montre.

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

Voir la mise à jour codepen

19
Viplock

C'est un comportement bien défini. Vous ne pouvez pas obtenir l'ancienne valeur d'un objet muté . En effet, newVal et oldVal font tous deux référence au même objet. Vue conservera pas une ancienne copie d'un objet que vous avez muté.

Si vous avait remplacé l'objet par un autre objet, Vue vous aurait fourni les références correctes.

Lisez la section Note dans docs. (vm.$watch)

Plus sur ceci ici et ici .

16
Quirk

C'est ce que j'utilise pour regarder en profondeur un objet. Mon exigence était de regarder les champs enfants de l'objet.

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