Vue.js - So suchen Sie ordnungsgemäß nach verschachtelten Daten

412

Ich versuche zu verstehen, wie man richtig auf Requisitenvariationen achtet. Ich habe eine übergeordnete Komponente (.vue-Dateien), die Daten von einem Ajax-Aufruf empfängt, die Daten in ein Objekt einfügt und damit eine untergeordnete Komponente über eine v-for-Direktive rendert, unter einer Vereinfachung meiner Implementierung:

<template>
    <div>
        <player v-for="(item, key, index) in players"
            :item="item"
            :index="index"
            :key="key"">
        </player>
    </div>
</template>

... dann im <script>Tag:

 data(){
     return {
         players: {}
 },
 created(){
        let self = this;
        this.$http.get('../serv/config/player.php').then((response) => {
            let pls = response.body;
            for (let p in pls) {
                self.$set(self.players, p, pls[p]);
            }
    });
}

Objektobjekte sind wie folgt:

item:{
   prop: value,
   someOtherProp: {
       nestedProp: nestedValue,
       myArray: [{type: "a", num: 1},{type: "b" num: 6} ...]
    },
}

Jetzt versuche ich in meiner untergeordneten "Player" -Komponente, nach den Eigenschaftsvarianten eines Elements zu suchen, und verwende Folgendes:

...
watch:{
    'item.someOtherProp'(newVal){
        //to work with changes in "myArray"
    },
    'item.prop'(newVal){
        //to work with changes in prop
    }
}

Es funktioniert, aber es scheint mir ein bisschen schwierig zu sein und ich habe mich gefragt, ob dies der richtige Weg ist. Mein Ziel ist es, jedes Mal eine Aktion auszuführen, wenn propsich myArrayneue Elemente oder Variationen innerhalb bestehender ändern oder erhalten. Jeder Vorschlag wird geschätzt.

Plastik
quelle
9
Verwenden "item.someOtherProp": function (newVal, oldVal){Sie einfach wie von @Ron vorgeschlagen.
Reiner
1
@ Reiner das war genau das, was er in der Frage zu vermeiden versuchte; Mit der ES5-Syntax ist es genauso. Das Betrachten eines berechneten Geräts verursacht keinen zusätzlichen Aufwand und ist in verschiedenen Situationen eine nützliche Technik.
craig_h
1
Ist es möglich, keysinnerhalb eines Objekts auf eine Veränderung zu achten? Beispiel:"item.*": function(val){...}
Charlie

Antworten:

621

Sie können dafür einen Deep Watcher verwenden :

watch: {
  item: {
     handler(val){
       // do stuff
     },
     deep: true
  }
}

Dadurch werden nun alle Änderungen an den Objekten im itemArray und Ergänzungen am Array selbst erkannt (bei Verwendung mit Vue.set ). Hier ist eine JSFiddle: http://jsfiddle.net/je2rw3rs/

BEARBEITEN

Wenn Sie nicht auf jede Änderung am Objekt der obersten Ebene achten möchten und nur eine weniger umständliche Syntax zum direkten Überwachen verschachtelter Objekte wünschen, können Sie computedstattdessen einfach Folgendes beobachten :

var vm = new Vue({
  el: '#app',
  computed: {
    foo() {
      return this.item.foo;
    }
  },
  watch: {
    foo() {
      console.log('Foo Changed!');
    }
  },
  data: {
    item: {
      foo: 'foo'
    }
  }
})

Hier ist die JSFiddle: http://jsfiddle.net/oa07r5fw/

craig_h
quelle
2
Auf diese Weise wird "Handler" aufgerufen, wenn eine Requisite eine Variation aufweist. Mein Zweck ist es, die Handler zu trennen, um zu beobachten, ob Änderungen an "Requisite" oder in myArray in "someOtherProp" separat vorgenommen wurden
Plastic
9
Wenn Sie nur auf Änderungen an bestimmten verschachtelten Objekten achten möchten, ist das, was Sie tun, in Ordnung. Wenn Sie eine weniger umständliche Syntax wünschen, können Sie computedstattdessen Folgendes ansehen : Folgendes jsfiddle.net/c52nda7x
craig_h
Genau das, wonach ich gesucht habe, jetzt scheint es mir so logisch: Erstellen Sie eine berechnete Eigenschaft, die die verschachtelte Requisite zurückgibt, und achten Sie darauf. Vielen Dank.
Kunststoff
Ich möchte nur hinzufügen, dass sich die zweite Option nicht wesentlich vom ursprünglichen Beobachter unterscheidet. Intern ist eine berechnete Eigenschaft nur ein Beobachter für alle referenzierten Objekte / Werte in der Funktion, der einen Datenwert auf das Ergebnis der Funktion setzt. - Sie machen den Code also wahrscheinlich nur mit dem gleichen Effekt etwas komplizierter.
Falco
22
@ Falco Ich habe die Antwort gerade in einem Kommentar hier entdeckt . Peerbolte gab an, dass dies möglich ist, indem der Name der Uhr in einfache Anführungszeichen gesetzt wird. Ich habe das getestet und es funktioniert. In diesem Fall wäre es also watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} ziemlich schlau. Ich liebe Vue!
Ron C.
436

Ein weiterer guter Ansatz, der etwas eleganter ist, lautet wie folgt:

 watch:{
     'item.someOtherProp': function (newVal, oldVal){
         //to work with changes in someOtherProp
     },
     'item.prop': function(newVal, oldVal){
         //to work with changes in prop
     }
 }

(Ich habe diesen Ansatz von @peerbolte im Kommentar hier gelernt )

Ron C.
quelle
47
viel eleganter (nicht nur ein bisschen): P. Dies sollte in der Dokumentation von Vue.js enthalten sein, da dies ein häufiger Anwendungsfall ist. Ich habe stundenlang nach einer Lösung gesucht, danke für Ihre Antwort.
Claudiu
11
@symba Es ist interessant, dass die Leute nicht bemerkt haben, dass dies das gleiche ist, was das OP vermeiden wollte, nur in der ES5Syntax. Wir könnten natürlich argumentieren, dass das ES2015 method definitionselbst nicht sehr elegant ist, aber es verfehlt irgendwie den Punkt der Frage, wie man vermeiden kann, den Namen in Anführungszeichen zu setzen. Ich vermute, die meisten Leute beschönigen die ursprüngliche Frage, die bereits die Antwort enthält, und suchen tatsächlich nach etwas, das, wie Sie sagen, nicht sehr gut dokumentiert ist
craig_h
1
@craig_h super interessanter Punkt. Ich war ursprünglich stundenlang auf der Jagd, um einen guten Weg zu finden, eine verschachtelte Requisite zu sehen. Und dieser Fragentitel war die nächste Frage, die ich gefunden habe, aber ich habe übersehen, dass er eine zitierte Requisite im Hauptteil der Frage aufgeführt hat, aber die Antworten nicht so elegant fand. Ich fand schließlich den zitierten Prop-Ansatz im Kommentar einer anderen Frage und war versucht, eine Frage zu erstellen, um sie selbst als Dokumentation des zitierten Prop-Ansatzes auf SO zu beantworten. Aber diese Frage war genau mein Titel, also habe ich die Antwort hier hinzugefügt. Vielleicht hätte ich eine neue Frage stellen sollen.
Ron C
9
@RonC Dies ist eine beliebte Frage, und Ihre Antwort ist genau das, wonach viele Leute suchen. Ich denke, sie passt hier gut. Wir alle haben nur eine begrenzte Zeit, und die meisten von uns haben die Fragen kaum gelesen. Dies war sicherlich keine Kritik an der Antwort, die Sie gegeben haben. Ich bin nur ein bisschen pedantisch, wenn ich darauf hinweise, dass meine ursprüngliche Antwort spezifisch für die war Frage und war nicht dazu gedacht, eine Meinung zu haben. Eigentlich mag ich den "Watching Computed" -Ansatz, aber viele bevorzugen den weniger verschlungenen "Quotes" -Ansatz, den Sie in Ihrer Antwort kurz zusammengefasst haben.
craig_h
10
Die offizielle Dokumentation enthält diesen Ansatz jetzt
feihcsim
19

VueJs tiefe Beobachtung in untergeordneten Objekten

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

Wie wäre es, wenn Sie eine Immobilie für eine Weile ansehen und dann wieder entfernen möchten?

Oder um eine untergeordnete Bibliothekseigenschaftseigenschaft zu beobachten?

Sie können den "dynamischen Beobachter" verwenden:

this.$watch(
 'object.property', //what you want to watch
 (newVal, oldVal) => {
    //execute your code here
 }
)

Das $watchgibt eine Unwatch-Funktion zurück, die die Überwachung beendet, wenn sie aufgerufen wird.

var unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()

Sie können auch die deepOption verwenden:

this.$watch(
'someObject', () => {
    //execute your code here
},
{ deep: true }
)

Bitte schauen Sie sich unbedingt die Dokumente an

roli roli
quelle
2
Laut den Dokumenten sollten Sie keine Note that you should not use an arrow function to define a watcher (e.g. searchQuery: newValue => this.updateAutocomplete(newValue)). The reason is arrow functions bind the parent context, so this will not be the Vue instance as you expect and this.updateAutocomplete will be undefined.
Pfeilfunktionen
7

Hier wird es nicht erwähnt, aber es ist auch möglich, das vue-property-decoratorMuster zu verwenden , wenn Sie Ihre VueKlasse erweitern.

import { Watch, Vue } from 'vue-property-decorator';

export default class SomeClass extends Vue {
   ...

   @Watch('item.someOtherProp')
   someOtherPropChange(newVal, oldVal) {
      // do something
   }

   ...
}
getglad
quelle
5

Eine andere Möglichkeit, hinzuzufügen, dass ich diese Lösung "gehackt" habe, bestand darin, Folgendes zu tun: Ich habe einen separaten computedWert eingerichtet, der einfach den Wert des verschachtelten Objekts zurückgibt.

data : function(){
    return {
        my_object : {
            my_deep_object : {
                my_value : "hello world";
            }.
        },
    };
},
computed : {
    helper_name : function(){
        return this.my_object.my_deep_object.my_value;
    },
},
watch : {
    helper_name : function(newVal, oldVal){
        // do this...
    }
}
Zac
quelle
gut;) aber was ist mit Arrays? :)
WEBCENTER
3

Mein Problem mit der akzeptierten Antwort der Verwendung deep: trueist, dass ich beim genauen Betrachten eines Arrays nicht leicht identifizieren kann, welches Element des Arrays die Änderung enthält. Die einzige klare Lösung, die ich gefunden habe, ist diese Antwort, in der erklärt wird, wie eine Komponente erstellt wird, damit Sie jedes Array-Element einzeln betrachten können.

krubo
quelle
1
Ja, das ist wahr, aber dafür ist ein Deep Watcher nicht gedacht (tatsächlich finden Sie oldValund newValbeide verweisen auf dasselbe Objekt, also sind sie gleich). Die Absicht von a deep watcherist es , etwas zu tun, wenn sich die Werte ändern, z. B. ein Ereignis ausgeben, einen Ajax-Aufruf ausführen usw. Andernfalls möchten Sie wahrscheinlich eine Komponente, wie in Manis Antwort ausgeführt.
craig_h
Ich hatte das gleiche Problem, die einzelnen Änderungen zu verfolgen! Ich fand zwar eine Lösung, und dokumentiert es hier .
Erik Koopmans
3

Verfolgung einzelner geänderter Elemente in einer Liste

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, oldVal) {
      // Handle changes here!
      // NOTE: For mutated objects, newVal and oldVal will be identical.
      console.log(newVal);
    },
  },
  created () {
    this.list.forEach((val) => {
      this.$watch(() => val, this.handleChange, {deep: true});
    });
  },
});

Wenn Ihre Liste nicht sofort ausgefüllt ist (wie in der ursprünglichen Frage), können Sie die Logik creatednach Bedarf verschieben, z.then() . Blocks.

Eine Änderungsliste ansehen

Wenn Ihre Liste selbst aktualisiert wird, um neue oder entfernte Elemente zu erhalten, habe ich ein nützliches Muster entwickelt, das die Liste selbst "flach" überwacht und Elemente dynamisch überwacht / deaktiviert, wenn sich die Liste ändert:

// NOTE: This example uses Lodash (_.differenceBy and _.pull) to compare lists
//       and remove list items. The same result could be achieved with lots of
//       list.indexOf(...) if you need to avoid external libraries.

var vm = new Vue({
  data: {
    list: [
      {name: 'obj1 to watch'},
      {name: 'obj2 to watch'},
    ],
    watchTracker: [],
  },
  methods: {
    handleChange (newVal, oldVal) {
      // Handle changes here!
      console.log(newVal);
    },
    updateWatchers () {
      // Helper function for comparing list items to the "watchTracker".
      const getItem = (val) => val.item || val;

      // Items that aren't already watched: watch and add to watched list.
      _.differenceBy(this.list, this.watchTracker, getItem).forEach((item) => {
        const unwatch = this.$watch(() => item, this.handleChange, {deep: true});
        this.watchTracker.push({ item: item, unwatch: unwatch });
        // Uncomment below if adding a new item to the list should count as a "change".
        // this.handleChange(item);
      });

      // Items that no longer exist: unwatch and remove from the watched list.
      _.differenceBy(this.watchTracker, this.list, getItem).forEach((watchObj) => {
        watchObj.unwatch();
        _.pull(this.watchTracker, watchObj);
        // Optionally add any further cleanup in here for when items are removed.
      });
    },
  },
  watch: {
    list () {
      return this.updateWatchers();
    },
  },
  created () {
    return this.updateWatchers();
  },
});
Erik Koopmans
quelle
0

Keine der Antworten für mich funktionierte. Eigentlich, wenn Sie verschachtelte Daten überwachen möchten, wobei Komponenten mehrmals aufgerufen werden. Deshalb werden sie mit verschiedenen Requisiten angerufen, um sie zu identifizieren. Zum Beispiel<MyComponent chart="chart1"/> <MyComponent chart="chart2"/> Meine Problemumgehung besteht darin, eine zusätzliche Vuex-Statusvariable zu erstellen, die ich manuell aktualisiere, um auf die zuletzt aktualisierte Eigenschaft zu verweisen.

Hier ist ein Implementierungsbeispiel für Vuex.ts:

export default new Vuex.Store({
    state: {
        hovEpacTduList: {},  // a json of arrays to be shared by different components, 
                             // for example  hovEpacTduList["chart1"]=[2,6,9]
        hovEpacTduListChangeForChart: "chart1"  // to watch for latest update, 
                                                // here to access "chart1" update 
   },
   mutations: {
        setHovEpacTduList: (state, payload) => {
            state.hovEpacTduListChangeForChart = payload.chart // we will watch hovEpacTduListChangeForChart
            state.hovEpacTduList[payload.chart] = payload.list // instead of hovEpacTduList, which vuex cannot watch
        },
}

Bei jeder Komponentenfunktion zum Aktualisieren des Speichers:

    const payload = {chart:"chart1", list: [4,6,3]}
    this.$store.commit('setHovEpacTduList', payload);

Jetzt auf jeder Komponente, um das Update zu erhalten:

    computed: {
        hovEpacTduListChangeForChart() {
            return this.$store.state.hovEpacTduListChangeForChart;
        }
    },
    watch: {
        hovEpacTduListChangeForChart(chart) {
            if (chart === this.chart)  // the component was created with chart as a prop <MyComponent chart="chart1"/> 
                console.log("Update! for", chart, this.$store.state.hovEpacTduList[chart]);
        },
    },
thomasL
quelle