Vue - Eine Reihe von Objekten genau beobachten und die Änderung berechnen?

108

Ich habe ein Array namens people, das Objekte wie folgt enthält:

Vor

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

Es kann sich ändern:

Nach dem

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

Beachten Sie, dass Frank gerade 33 geworden ist.

Ich habe eine App, in der ich versuche, das Personenarray zu beobachten. Wenn sich einer der Werte ändert, protokolliere ich die Änderung:

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

Ich habe dies auf die Frage gestützt , die ich gestern zu Array-Vergleichen gestellt und die am schnellsten funktionierende Antwort ausgewählt habe.

An diesem Punkt erwarte ich also ein Ergebnis von: { id: 1, name: 'Frank', age: 33 }

Aber alles, was ich zurück in die Konsole bekomme, ist (wenn man bedenkt, dass ich es in einer Komponente hatte):

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

Und in dem Codepen, den ich erstellt habe , ist das Ergebnis ein leeres Array und nicht das geänderte Objekt, das sich geändert hat, was ich erwartet hätte.

Wenn jemand vorschlagen könnte, warum dies geschieht oder wo ich hier falsch gelaufen bin, wäre er sehr dankbar, vielen Dank!

Craig van Tonder
quelle

Antworten:

136

Ihre Vergleichsfunktion zwischen altem und neuem Wert weist ein Problem auf. Es ist besser, die Dinge nicht so kompliziert zu machen, da dies Ihren Debugging-Aufwand später erhöht. Sie sollten es einfach halten.

Der beste Weg ist, eine zu erstellen person-componentund jede Person separat in ihrer eigenen Komponente zu beobachten, wie unten gezeigt:

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

Nachfolgend finden Sie ein Arbeitsbeispiel zum Betrachten der Personenkomponente. Wenn Sie es auf der übergeordneten Seite behandeln möchten, können Sie $emitein Ereignis nach oben senden, das das idder geänderten Person enthält.

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>

Mani
quelle
Das ist zwar eine funktionierende Lösung, entspricht aber nicht ganz meinem Anwendungsfall. Sie sehen, in Wirklichkeit habe ich die App und eine Komponente, die Komponente verwendet eine Vue-Material-Tabelle und listet die Daten mit der Möglichkeit auf, die Werte inline zu bearbeiten. Ich versuche, einen der Werte zu ändern, und überprüfe dann, was sich geändert hat. In diesem Fall werden die Vorher- und Nachher-Arrays tatsächlich verglichen, um festzustellen, welcher Unterschied besteht. Könnte ich Ihre Lösung implementieren, um das Problem zu lösen? In der Tat könnte ich dies wahrscheinlich tun, aber ich habe nur das Gefühl, dass es gegen den Fluss dessen wirken würde, was in dieser Hinsicht in Vue-Material verfügbar ist
Craig van Tonder
2
Übrigens, danke, dass Sie sich die Zeit genommen haben, dies zu erklären. Es hat mir geholfen, mehr über Vue zu erfahren, was ich sehr schätze!
Craig van Tonder
Ich habe eine Weile gebraucht, um das zu verstehen, aber du hast absolut Recht, das funktioniert wie ein Zauber und ist die richtige Art, Dinge zu tun, wenn du Verwirrung und weitere Probleme vermeiden willst :)
Craig van Tonder
1
Ich habe das auch bemerkt und hatte den gleichen Gedanken, aber was auch im Objekt enthalten ist, ist der Wertindex, der den Wert enthält, die Getter und Setter sind da, aber im Vergleich ignoriert er sie, weil ich denke, dass dies nicht der Fall ist keine Prototypen bewerten. Eine der anderen Antworten liefert den Grund, warum es nicht funktionieren würde, weil newVal und oldVal dasselbe waren, es ist ein bisschen kompliziert, aber es ist etwas, das an einigen Stellen angesprochen wurde, und eine andere Antwort bietet eine anständige Lösung für das einfache Erstellen ein unveränderliches Objekt zu Vergleichszwecken.
Craig van Tonder
1
Letztendlich ist Ihr Weg jedoch auf einen Blick leichter zu verstehen und bietet mehr Flexibilität in Bezug auf das, was verfügbar ist, wenn sich der Wert ändert. Es hat mir sehr geholfen zu verstehen, welche Vorteile es hat, es in Vue einfach zu halten, aber ich habe mich ein wenig damit beschäftigt, wie Sie in meiner anderen Frage gesehen haben. Danke vielmals! :)
Craig van Tonder
21

Ich habe die Implementierung geändert, um Ihr Problem zu lösen. Ich habe ein Objekt erstellt, um die alten Änderungen zu verfolgen und damit zu vergleichen. Sie können es verwenden, um Ihr Problem zu lösen.

Hier habe ich eine Methode erstellt, bei der der alte Wert in einer separaten Variablen gespeichert und dann in einer Uhr verwendet wird.

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

Siehe den aktualisierten Codepen

Druckverschluss
quelle
Wenn es gemountet ist, speichern Sie eine Kopie der Daten und vergleichen Sie diese damit. Interessant, aber mein Anwendungsfall wäre komplexer und ich bin mir nicht sicher, wie das beim Hinzufügen und Entfernen von Objekten zum Array funktionieren würde. @Quirk lieferte gute Links, um das Problem ebenfalls zu lösen. Aber ich wusste nicht, dass Sie verwenden können vm.$data, danke!
Craig van Tonder
Ja, und ich aktualisiere es nach der Uhr auch, indem ich die Methode erneut aufrufe. Wenn Sie zum ursprünglichen Wert zurückkehren, wird auch die Änderung verfolgt.
Viplock
Ohhh, ich habe nicht bemerkt, dass das Verstecken dort viel Sinn macht und eine weniger komplizierte Art ist, damit umzugehen (im Gegensatz zur Lösung auf Github).
Craig van Tonder
und ya, wenn Sie etwas zum ursprünglichen Array hinzufügen oder daraus entfernen, rufen Sie die Methode einfach erneut auf und Sie können die Lösung erneut verwenden.
Viplock
1
_.cloneDeep () hat in meinem Fall wirklich geholfen. Danke dir!! Wirklich hilfreich!
Cristiana Pereira
18

Es ist gut definiertes Verhalten. Sie können den alten Wert für ein mutiertes Objekt nicht abrufen . Das liegt daran, dass sich sowohl das newValals auch oldValauf dasselbe Objekt beziehen. Vue wird nicht eine alte Kopie eines Objekts halten , dass Sie mutiert.

Hatten Sie ersetzt das Objekt mit einem anderen, würde Vue Sie haben mit der richtigen Referenzen zur Verfügung gestellt.

Lesen Sie den NoteAbschnitt in den Dokumenten. ( vm.$watch)

Mehr dazu hier und hier .

Marotte
quelle
3
Oh mein Hut, vielen Dank! Das ist eine knifflige Angelegenheit ... Ich habe völlig erwartet, dass val und oldVal unterschiedlich sind, aber nachdem ich sie überprüft habe, sehe ich, dass es zwei Kopien des neuen Arrays sind, die es vorher nicht verfolgt. Lesen Sie ein bisschen mehr und fanden Sie diese unbeantwortete SO-Frage bezüglich des gleichen Missverständnisses: stackoverflow.com/questions/35991494/…
Craig van Tonder
5

Dies ist, was ich benutze, um ein Objekt tief zu beobachten. Meine Anforderung bestand darin, die untergeordneten Felder des Objekts zu beobachten.

new Vue({
    el: "#myElement",
    data:{
        entity: {
            properties: []
        }
    },
    watch:{
        'entity.properties': {
            handler: function (after, before) {
                // Changes detected.    
            },
            deep: true
        }
    }
});
Alper Ebicoglu
quelle
Ich glaube , dass man das Verständnis der Cavet könnte fehlen , die in beschrieben wurde stackoverflow.com/a/41136186/2110294 . Um ganz klar zu sein, dies ist keine Lösung für die Frage und funktioniert nicht so, wie Sie es in bestimmten Situationen erwartet haben.
Craig van Tonder
genau das habe ich gesucht!. Danke
Jaydeep Shil
Das gleiche hier, genau das, was ich brauchte !! Vielen Dank.
Guntar
4

Die Komponentenlösung und die Deep-Clone-Lösung haben ihre Vorteile, aber auch Probleme:

  1. Manchmal möchten Sie Änderungen an abstrakten Daten verfolgen - es ist nicht immer sinnvoll, Komponenten um diese Daten herum zu erstellen.

  2. Das Deep-Cloning Ihrer gesamten Datenstruktur bei jeder Änderung kann sehr teuer sein.

Ich denke, es gibt einen besseren Weg. Wenn Sie alle Elemente in einer Liste anzeigen möchten und wissen möchten, welches Element in der Liste geändert wurde, können Sie benutzerdefinierte Beobachter für jedes Element separat einrichten, z. B.:

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

Mit dieser Struktur handleChange()erhalten Sie das spezifische Listenelement, das sich geändert hat - von dort aus können Sie jede gewünschte Behandlung ausführen.

Ich habe hier auch ein komplexeres Szenario dokumentiert , falls Sie Elemente zu Ihrer Liste hinzufügen / entfernen (anstatt nur die bereits vorhandenen Elemente zu bearbeiten).

Erik Koopmans
quelle
Vielen Dank, Erik, Sie präsentieren gültige Punkte und die bereitgestellte Methodik ist auf jeden Fall nützlich, wenn sie als Lösung für die Frage implementiert wird.
Craig van Tonder