Vue: Stai guardando in profondità una serie di oggetti e calcoli il cambiamento?


108

Ho chiamato un array people che contiene oggetti come segue:

Prima

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

Può cambiare:

Dopo

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

Notate che Frank ha appena compiuto 33 anni.

Ho un'app in cui cerco di guardare l'array di persone e quando uno qualsiasi dei valori cambia, quindi registro la modifica:

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

Ho basato questo sulla domanda che ho posto ieri sui confronti degli array e selezionato la risposta di lavoro più rapida.

Quindi, a questo punto mi aspetto di vedere un risultato di: { id: 1, name: 'Frank', age: 33 }

Ma tutto quello che ricevo nella console è (tenendo presente che l'avevo in un componente):

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

E nel codepen che ho creato , il risultato è un array vuoto e non l'oggetto modificato che è cambiato, che sarebbe quello che mi aspettavo.

Se qualcuno potesse suggerire perché questo sta accadendo o dove ho sbagliato qui, sarebbe molto apprezzato, molte grazie!

Risposte:


136

La tua funzione di confronto tra il vecchio valore e il nuovo valore sta avendo qualche problema. È meglio non complicare così tanto le cose, poiché aumenterà lo sforzo di debug in seguito. Dovresti mantenerlo semplice.

Il modo migliore è creare person-componente guardare ogni persona separatamente all'interno del proprio componente, come mostrato di seguito:

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

Di seguito è riportato un esempio funzionante per guardare il componente interno della persona. Se vuoi gestirlo da parte dei genitori, puoi usare $emitper inviare un evento verso l'alto, contenente la idpersona modificata.

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>


Questa è davvero una soluzione funzionante ma non è del tutto conforme al mio caso d'uso. Vedete, in realtà ho l'app e un componente, il componente utilizza la tabella vue-material ed elenca i dati con la possibilità di modificare i valori in linea. Sto cercando di modificare uno dei valori, quindi controllare cosa è cambiato, quindi in questo caso, confronta effettivamente gli array prima e dopo per vedere quale differenza c'è. Posso implementare la tua soluzione per risolvere il problema? In effetti potrei probabilmente farlo, ma sento che funzionerebbe contro il flusso di ciò che è disponibile a questo proposito all'interno di vue-material
Craig van Tonder

2
A proposito, grazie per aver dedicato del tempo per spiegare questo, mi ha aiutato a saperne di più su Vue che apprezzo!
Craig van Tonder

Mi ci è voluto un po 'per comprenderlo ma hai assolutamente ragione, funziona come un fascino ed è il modo corretto di fare le cose se vuoi evitare confusione e ulteriori problemi :)
Craig van Tonder

1
Ho notato anche questo e ho avuto lo stesso pensiero ma quello che è contenuto anche nell'oggetto è l'indice del valore che contiene il valore, i getter e i setter ci sono ma in confronto li ignora, per mancanza di una migliore comprensione penso che lo sia non valutare su eventuali prototipi. Una delle altre risposte fornisce il motivo per cui non avrebbe funzionato, è perché newVal e oldVal erano la stessa cosa, è un po 'complicato ma è qualcosa che è stato affrontato in alcuni punti, ancora un'altra risposta fornisce un lavoro decente per una facile creazione un oggetto immutabile a scopo di confronto.
Craig van Tonder

1
In definitiva, tuttavia, il tuo modo è più facile da comprendere a colpo d'occhio e offre maggiore flessibilità in termini di ciò che è disponibile quando il valore cambia. Mi ha aiutato molto a capire i vantaggi di mantenerlo semplice in Vue, ma mi sono bloccato un po 'come hai visto nell'altra mia domanda. Grazie molto! :)
Craig van Tonder

21

Ho modificato l'implementazione di esso per risolvere il tuo problema, ho creato un oggetto per tenere traccia delle vecchie modifiche e confrontarlo con quello. Puoi usarlo per risolvere il tuo problema.

Qui ho creato un metodo, in cui il vecchio valore verrà memorizzato in una variabile separata e, che poi verrà utilizzato in un orologio.

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

Vedi il codepen aggiornato


Quindi, quando è montato, archivia una copia dei dati e usala per confrontarli. Interessante, ma il mio caso d'uso sarebbe più complesso e non sono sicuro di come funzionerebbe quando si aggiungono e rimuovono oggetti dall'array, @Quirk ha fornito anche buoni collegamenti per risolvere il problema. Ma non sapevo che puoi usare vm.$data, grazie!
Craig van Tonder

sì, e lo sto aggiornando dopo l'orologio anche chiamando di nuovo il metodo, da esso se torni al valore originale, anche esso terrà traccia del cambiamento.
Viplock

Ohhh, non ho notato che nascondersi lì ha molto senso ed è un modo meno complicato per affrontare questo problema (al contrario della soluzione su GitHub).
Craig van Tonder

e sì, se stai aggiungendo o rimuovendo qualcosa dall'array originale, chiama di nuovo il metodo e sei a posto con la soluzione.
Viplock

1
_.cloneDeep () ha davvero aiutato nel mio caso. Grazie!! Davvero utile!
Cristiana Pereira

18

È un comportamento ben definito. Non è possibile ottenere il vecchio valore per un oggetto mutato . Questo perché sia ​​il newValche si oldValriferiscono allo stesso oggetto. Vue non conserverà una vecchia copia di un oggetto che hai mutato.

Se avessi sostituito l'oggetto con un altro, Vue ti avrebbe fornito i riferimenti corretti.

Leggi la Notesezione nei documenti. ( vm.$watch)

Maggiori informazioni su questo qui e qui .


3
Oh mio cappello, grazie mille! Questo è complicato ... Mi aspettavo completamente che val e oldVal fossero diversi ma dopo averli ispezionati vedo che sono due copie del nuovo array, non ne tiene traccia prima. Leggi ancora un po 'e ho trovato questa domanda SO senza risposta riguardo lo stesso malinteso: stackoverflow.com/questions/35991494/…
Craig van Tonder

5

Questo è ciò che uso per osservare in profondità un oggetto. La mia esigenza era guardare i campi figlio dell'oggetto.

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

Credo che potresti perdere la comprensione del cavet descritto in stackoverflow.com/a/41136186/2110294 . Giusto per essere chiari, questa non è una soluzione alla domanda e non funzionerà come ci si potrebbe aspettare in determinate situazioni.
Craig van Tonder il

questo è esattamente quello che stavo cercando !. Grazie
Jaydeep Shil

Lo stesso qui, esattamente quello di cui avevo bisogno !! Grazie.
Guntar

4

La soluzione dei componenti e la soluzione di clonazione profonda hanno i loro vantaggi, ma presentano anche problemi:

  1. A volte si desidera tenere traccia delle modifiche nei dati astratti: non ha sempre senso creare componenti attorno a tali dati.

  2. La clonazione profonda dell'intera struttura dati ogni volta che apporti una modifica può essere molto costosa.

Penso che ci sia un modo migliore. Se desideri 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) {
      // Handle changes here!
      console.log(newVal);
    },
  },
  created () {
    this.list.forEach((val) => {
      this.$watch(() => val, this.handleChange, {deep: true});
    });
  },
});

Con questa struttura, handleChange() riceverai la voce dell'elenco specifica che è cambiata: da lì puoi eseguire qualsiasi operazione desideri.

Ho anche documentato uno scenario più complesso qui , nel caso in cui tu stia aggiungendo / rimuovendo elementi dalla tua lista (piuttosto che manipolare solo gli elementi già presenti).


Grazie Erik, presenti punti validi e la metodologia fornita è sicuramente utile se implementata come soluzione alla domanda.
Craig van Tonder,
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.