Vue - Głębokie obserwowanie szeregu obiektów i obliczanie zmiany?

108

Mam tablicę o nazwie, peoplektóra zawiera następujące obiekty:

Przed

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

Może się zmienić:

Po

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

Zauważ, że Frank właśnie skończył 33 lata.

Mam aplikację, w której próbuję obserwować tablicę osób i gdy dowolna z wartości zmienia się, rejestruję zmianę:

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

Oparłem się na pytaniu, które zadałem wczoraj na temat porównań tablic i wybrałem najszybszą działającą odpowiedź.

Tak więc w tym miejscu spodziewam się wyniku: { id: 1, name: 'Frank', age: 33 }

Ale wszystko, co otrzymuję w konsoli, to (pamiętając, że miałem ją w komponencie):

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

A w kodowaniu, które stworzyłem , wynikiem jest pusta tablica, a nie zmieniony obiekt, który się zmienił, czego się spodziewałem.

Gdyby ktoś mógł zasugerować, dlaczego tak się dzieje lub gdzie popełniłem błąd, byłoby to bardzo wdzięczne, wielkie dzięki!

Craig van Tonder
źródło

Odpowiedzi:

136

Twoja funkcja porównania między starą a nową wartością ma pewien problem. Lepiej nie komplikować sprawy tak bardzo, ponieważ później zwiększy to wysiłek związany z debugowaniem. Powinieneś to uprościć.

Najlepszym sposobem jest utworzenie person-componenti obserwowanie każdej osoby z osobna wewnątrz jej własnego komponentu, jak pokazano poniżej:

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

Poniżej znajduje się działający przykład oglądania komponentu osoby wewnętrznej. Jeśli chcesz zająć się tym po stronie rodzica, możesz użyć, $emitaby wysłać zdarzenie w górę, zawierające idzmodyfikowaną osobę.

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
źródło
To rzeczywiście działające rozwiązanie, ale nie jest to całkowicie zgodne z moim przypadkiem użycia. Widzisz, w rzeczywistości mam aplikację i jeden komponent, komponent używa tabeli vue-material i wyświetla dane z możliwością edycji wartości w linii. Próbuję zmienić jedną z wartości, a następnie sprawdzić, co się zmieniło, więc w tym przypadku faktycznie porównuje tablice przed i po, aby zobaczyć, jaka jest różnica. Czy mogę wdrożyć Twoje rozwiązanie, aby rozwiązać problem? Rzeczywiście mógłbym to zrobić, ale po prostu czuję, że działałoby to wbrew przepływowi tego, co jest dostępne w tym zakresie w materiale vue
Craig van Tonder,
2
Przy okazji, dziękuję za poświęcenie czasu na wyjaśnienie, pomogło mi to dowiedzieć się więcej o Vue, co bardzo cenię!
Craig van Tonder,
Zrozumienie tego zajęło mi trochę czasu, ale masz całkowitą rację, to działa jak urok i jest właściwym sposobem robienia rzeczy, jeśli chcesz uniknąć nieporozumień i dalszych problemów :)
Craig van Tonder,
1
Zauważyłem to również i miałem tę samą myśl, ale to, co jest zawarte w obiekcie, to indeks wartości, który zawiera wartość, są tam pobierające i ustawiające, ale w porównaniu je ignoruje, z powodu braku lepszego zrozumienia myślę, że tak nie oceniać na żadnych prototypach. Jedna z pozostałych odpowiedzi podaje powód, dla którego to nie zadziała, ponieważ newVal i oldVal to to samo, jest to trochę skomplikowane, ale jest to coś, o czym mówiono w kilku miejscach, jeszcze inna odpowiedź zapewnia przyzwoitą pracę w celu łatwego tworzenia niezmienny obiekt do celów porównawczych.
Craig van Tonder
1
Ostatecznie jednak Twój sposób jest łatwiejszy do zrozumienia na pierwszy rzut oka i zapewnia większą elastyczność w zakresie tego, co jest dostępne, gdy zmienia się wartość. Bardzo pomogło mi to zrozumieć korzyści płynące z prostoty obsługi w Vue, ale utknąłem z tym trochę, jak widzieliście w moim drugim pytaniu. Wielkie dzięki! :)
Craig van Tonder
21

Zmieniłem implementację, aby rozwiązać twój problem, zrobiłem obiekt, aby śledzić stare zmiany i porównać go z tym. Możesz go użyć do rozwiązania problemu.

Tutaj stworzyłem metodę, w której stara wartość będzie przechowywana w osobnej zmiennej i która następnie zostanie wykorzystana w zegarku.

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

Zobacz zaktualizowany kodeks

Viplock
źródło
Po zamontowaniu zapisz kopię danych i użyj jej do porównania. Interesujące, ale mój przypadek użycia byłby bardziej złożony i nie jestem pewien, jak to działałoby podczas dodawania i usuwania obiektów z tablicy, @Quirk zapewnia dobre linki do rozwiązania problemu. Ale nie wiedziałem, że możesz użyć vm.$data, dziękuję!
Craig van Tonder,
tak, i aktualizuję go po zegarku również przez ponowne wywołanie metody, przez to, jeśli wrócisz do pierwotnej wartości, to również będzie śledzić zmianę.
Viplock,
Och, nie zauważyłem, że ukrywanie się tam ma dużo sensu i jest mniej skomplikowanym sposobem radzenia sobie z tym (w przeciwieństwie do rozwiązania na githubie).
Craig van Tonder,
i ya, jeśli dodajesz lub usuwasz coś z oryginalnej tablicy, po prostu wywołaj metodę ponownie i możesz ponownie przejść z rozwiązaniem.
Viplock
1
_.cloneDeep () naprawdę pomogło w moim przypadku. Dziękuję Ci!! Naprawdę pomocny!
Cristiana Pereira,
18

Jest to dobrze zdefiniowane zachowanie. Nie można uzyskać starej wartości dla zmutowanego obiektu. Dzieje się tak, ponieważ zarówno atrybuty, jak newVali oldValodnoszą się do tego samego obiektu. Vue nie zachowa starej kopii obiektu, który zmutowałeś.

Gdybyś wymienił obiekt na inny, Vue dostarczyłby ci poprawnych referencji.

Przeczytaj Notesekcję w dokumentacji. ( vm.$watch)

Więcej na ten temat tutaj i tutaj .

Dziwactwo
źródło
3
O mój kapelusz, wielkie dzięki! To jest trudne ... Całkowicie spodziewałem się, że val i oldVal będą różne, ale po ich sprawdzeniu widzę, że to dwie kopie nowej tablicy, wcześniej jej nie śledził. Przeczytaj trochę więcej i znalazłem to bez odpowiedzi pytanie SO dotyczące tego samego nieporozumienia: stackoverflow.com/questions/35991494/…
Craig van Tonder
5

To jest to, czego używam do głębokiego oglądania obiektu. Moim wymaganiem było obserwowanie pól potomnych obiektu.

new Vue({
    el: "#myElement",
    data:{
        entity: {
            properties: []
        }
    },
    watch:{
        'entity.properties': {
            handler: function (after, before) {
                // Changes detected.    
            },
            deep: true
        }
    }
});
Alper Ebicoglu
źródło
Uważam, że możesz nie rozumieć jaskini opisanej na stackoverflow.com/a/41136186/2110294 . Dla jasności, to nie jest rozwiązanie tego pytania i nie zadziała tak, jak można się spodziewać w pewnych sytuacjach.
Craig van Tonder
właśnie tego szukałem !. Dzięki
Jaydeep Shil
To samo tutaj, dokładnie to, czego potrzebowałem !! Dzięki.
Guntar
4

Rozwiązanie komponentu i rozwiązanie głębokiego klonu mają swoje zalety, ale mają również problemy:

  1. Czasami chcesz śledzić zmiany w abstrakcyjnych danych - nie zawsze ma sens budowanie komponentów wokół tych danych.

  2. Głębokie klonowanie całej struktury danych za każdym razem, gdy wprowadzasz zmianę, może być bardzo kosztowne.

Myślę, że jest lepszy sposób. Jeśli chcesz obserwować wszystkie elementy na liście i wiedzieć, który element na liście się zmienił, możesz skonfigurować niestandardowe obserwatory dla każdego elementu osobno, na przykład:

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

Dzięki tej strukturze handleChange()otrzymasz określoną pozycję listy, która uległa zmianie - stamtąd możesz wykonać dowolną obsługę.

Udokumentowałem tutaj również bardziej złożony scenariusz , na wypadek, gdybyś dodawał / usuwał elementy do swojej listy (zamiast tylko manipulować przedmiotami już tam istniejącymi).

Erik Koopmans
źródło
Dzięki Erik, przedstawiasz ważne punkty, a przedstawiona metodologia jest z pewnością przydatna, jeśli zostanie wdrożona jako rozwiązanie problemu.
Craig van Tonder