Vue.js - Come controllare correttamente i dati nidificati


414

Sto cercando di capire come guardare correttamente per qualche variazione di prop. Ho un componente genitore (file .vue) che riceve dati da una chiamata Ajax, inserisce i dati all'interno di un oggetto e li usa per rendere alcuni componenti figlio attraverso una direttiva v-for, sotto una semplificazione della mia implementazione:

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

... quindi all'interno del <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]);
            }
    });
}

gli oggetti oggetto sono così:

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

Ora, all'interno del componente "player" di mio figlio, sto cercando di verificare la variazione delle proprietà di qualsiasi articolo e utilizzo:

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

Funziona ma mi sembra un po 'complicato e mi chiedevo se questo è il modo giusto di farlo. Il mio obiettivo è quello di eseguire alcune azioni ogni volta che propcambia o myArrayottiene nuovi elementi o alcune variazioni all'interno di quelli esistenti. Ogni suggerimento sarà apprezzato.


9
Usa "item.someOtherProp": function (newVal, oldVal){come suggerito @Ron.
Reiner,

1
@Reiner questo era esattamente ciò che stava cercando di evitare nella domanda; è esattamente la stessa cosa usando la sintassi ES5. In realtà non c'è alcun sovraccarico aggiuntivo nel guardare un computer ed è una tecnica utile in varie situazioni.
craig_h,

1
È possibile guardare per un cambiamento in tutto keysall'interno di un oggetto? Esempio:"item.*": function(val){...}
Charlie

Risposte:


623

Puoi usare un deep watcher per questo:

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

Questo ora rileverà eventuali modifiche agli oggetti itemnell'array e aggiunte all'array stesso (se usato con Vue.set ). Ecco un JSFiddle: http://jsfiddle.net/je2rw3rs/

MODIFICARE

Se non vuoi guardare per ogni modifica sull'oggetto di livello superiore e vuoi solo una sintassi meno imbarazzante per guardare direttamente gli oggetti nidificati, puoi semplicemente guardare un computedinvece:

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

Ecco il JSFiddle: http://jsfiddle.net/oa07r5fw/


2
in questo modo "handler" verrà invocato ogni qualvolta un prop ha una variazione, il mio scopo è quello di separare i gestori al fine di osservare se i cambiamenti accadono in "prop" o all'interno di myArray in "someOtherProp" separatamente
Plastic

9
Se vuoi solo controllare le modifiche su specifici oggetti nidificati, allora quello che stai facendo va bene. Se vuoi una sintassi meno imbarazzante, potresti computedinvece guardarne
craig_h

Esattamente quello che stavo cercando, ora mi sembra così logico: crea una proprietà calcolata che restituisca il puntello nidificato e guardalo. Grazie mille.
Plastica,

Voglio solo aggiungere che la seconda opzione non è molto diversa dall'originale. Internamente una proprietà calcolata è solo un watcher su tutti gli oggetti / valori di riferimento nella funzione che imposta un valore di dati sul risultato della funzione. - Quindi probabilmente renderai il codice solo un po 'più contorto con lo stesso effetto.
Falco,

22
@Falco Ho appena scoperto la risposta in un commento qui . peerbolte ha indicato che può essere fatto inserendo il nome dell'orologio tra virgolette singole. Ho provato questo e funziona. Quindi in questo caso sarebbe watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} piuttosto elegante. Adoro Vue!
Ron C,

437

Un altro buon approccio e un po 'più elegante è il seguente:

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

(Ho imparato questo approccio da @peerbolte nel commento qui )


47
molto più elegante (non solo un po '): P. Questo dovrebbe essere nella documentazione di Vue.js in quanto è un caso d'uso comune. Ho cercato per ore una soluzione, grazie per la tua risposta.
Claudiu,

11
@symba È interessante che le persone non abbiano notato che questa è la stessa cosa che l'OP ha chiesto di evitare, proprio in ES5sintassi. Ovviamente potremmo sostenere che lo ES2015 method definitionstesso non è molto elegante, ma in qualche modo manca il punto della domanda, che è come evitare di racchiudere il nome tra virgolette. Immagino che la maggior parte delle persone stia ignorando la domanda originale, che contiene già la risposta, e in realtà stanno cercando qualcosa che, come dici tu, non è ben documentato,
craig_h

1
@craig_h punto super interessante. Inizialmente ero a caccia di ore per trovare un buon modo per guardare un oggetto nidificato. E questo titolo della domanda era la domanda più vicina che ho trovato, ma mi mancava che elencasse un oggetto di scena citato nel corpo della domanda, ma trovava le risposte non così eleganti. Alla fine ho trovato l'approccio di prop citato nel commento di un'altra domanda e sono stato tentato di creare una domanda solo per rispondermi come documentazione dell'approccio di prop citato su SO. Ma questa domanda era esattamente quale sarebbe stato il mio titolo, quindi ho aggiunto la risposta qui. Forse avrei dovuto creare una nuova domanda.
Ron C,

9
@RonC Questa è una domanda popolare e la tua risposta è esattamente ciò che molte persone stanno cercando, quindi penso che si trovi bene qui. Tutti noi abbiamo solo un tempo limitato e la maggior parte di noi legge a malapena le domande, quindi questa sicuramente non è stata una critica alla risposta che hai fornito, sono solo un po 'pedante nel sottolineare che la mia risposta originale era specifica per domanda e non intendeva essere supponente. In realtà mi piace l'approccio "guardare calcolato", tuttavia molti preferiscono l'approccio "contorto" meno contorto, che hai sinteticamente sintetizzato nella tua risposta.
craig_h,

10
La documentazione ufficiale ora include questo approccio
feihcsim,

20

VueJs deep watch in oggetti figlio

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

8

E se vuoi guardare una proprietà per un po 'e poi non guardarla?

O per guardare una proprietà del componente figlio della libreria?

Puoi utilizzare il "watcher dinamico":

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

Il $watchrestituisce una funzione unwatch che si fermerà a guardare se viene chiamato.

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

Inoltre puoi usare l' deepopzione:

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

Assicurati di dare un'occhiata ai documenti


2
Secondo i documenti , non dovresti usare le funzioni freccia per definire un osservatore: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.
wonder95

7

Non vederlo menzionato qui, ma è anche possibile utilizzare il vue-property-decoratormodello se si sta estendendo la Vueclasse.

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

export default class SomeClass extends Vue {
   ...

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

   ...
}

5

Un altro modo per aggiungere che ho usato per "hackerare" questa soluzione era quello di fare questo: ho impostato un computedvalore separato che avrebbe semplicemente restituito il valore dell'oggetto nidificato.

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

buono;) ma con gli array? :)
WEBCENTER

3

Il mio problema con la risposta accettata dell'utilizzo deep: trueè che quando si guarda in profondità un array, non riesco facilmente a identificare quale elemento dell'array contiene la modifica. L'unica soluzione chiara che ho trovato è questa risposta, che spiega come creare un componente in modo da poter guardare ogni elemento dell'array singolarmente.


1
Sì, è vero, ma non è quello che fa un osservatore profondo (in effetti troverai oldValed newValentrambi far riferimento allo stesso oggetto, quindi sono gli stessi). L'intenzione di a deep watcherè fare qualcosa quando cambiano i valori, come emettere un evento, effettuare una chiamata ajax ecc. Altrimenti, probabilmente si desidera un componente, come sottolineato nella risposta di Mani.
craig_h,

Ho avuto lo stesso problema nel tenere traccia delle singole modifiche! Tuttavia ho trovato una soluzione e l'ho documentata qui .
Erik Koopmans,

3

Tracciamento di singoli elementi modificati in un elenco

Se vuoi guardare tutti gli elementi in un elenco e sapere quale elemento nell'elenco è cambiato, puoi impostare osservatori personalizzati su ogni elemento separatamente, in questo modo:

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

Se la tua lista non è popolata immediatamente (come nella domanda originale), puoi spostare la logica createdda dove necessario, ad esempio all'interno del .then()blocco.

Guardando un elenco di modifica

Se la tua stessa lista si aggiorna per avere elementi nuovi o rimossi, ho sviluppato un modello utile che "superficiale" controlla la lista stessa e guarda / sblocca dinamicamente gli oggetti quando la lista cambia:

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

0

Nessuna delle risposte per me funzionava. In realtà, se si desidera controllare i dati nidificati con i componenti chiamati più volte. Quindi vengono chiamati con diversi oggetti di scena per identificarli. Ad esempio, la <MyComponent chart="chart1"/> <MyComponent chart="chart2"/> mia soluzione alternativa è quella di creare una variabile di stato Vuex aggiuntiva, che aggiorno manualmente per puntare alla proprietà che è stata aggiornata per l'ultima volta.

Ecco un esempio di implementazione di 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
        },
}

Su qualsiasi funzione Componente per aggiornare il negozio:

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

Ora su qualsiasi componente per ottenere l'aggiornamento:

    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]);
        },
    },
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.