Vue.js - Jak prawidłowo obserwować zagnieżdżone dane

413

Staram się zrozumieć, jak prawidłowo obserwować niektóre odmiany rekwizytów. Mam komponent nadrzędny (pliki .vue), który odbiera dane z wywołania ajax, umieszczam dane w obiekcie i używam go do renderowania komponentu podrzędnego za pomocą dyrektywy v-for, poniżej uproszczenia mojej implementacji:

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

... a następnie wewnątrz <script>tagu:

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

obiekty przedmiotów są takie:

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

Teraz w komponencie mojego „odtwarzacza” mojego dziecka próbuję obserwować zmiany właściwości dowolnego przedmiotu i używam:

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

Działa, ale wydaje mi się to trochę trudne i zastanawiałem się, czy to właściwy sposób, aby to zrobić. Moim celem jest wykonywanie pewnych działań za każdym razem, gdy propzmieniają się lub myArrayotrzymują nowe elementy lub zmiany w istniejących. Wszelkie sugestie będą mile widziane.

Plastikowy
źródło
9
Po prostu użyj "item.someOtherProp": function (newVal, oldVal){zgodnie z sugestią @Ron.
Reiner
1
@ Reiner tego właśnie chciał uniknąć w pytaniu; tak samo jest ze składnią ES5. W rzeczywistości nie ma żadnych dodatkowych kosztów związanych z oglądaniem komputera i jest to przydatna technika w różnych sytuacjach.
craig_h,
1
Czy można obserwować zmianę we keyswnętrzu obiektu? Przykład:"item.*": function(val){...}
Charlie,

Odpowiedzi:

622

Możesz do tego użyć głębokiego obserwatora :

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

Spowoduje to teraz wykrycie wszelkich zmian w obiektach w itemtablicy i dodatków do samej tablicy (w przypadku użycia z Vue.set ). Oto JSFiddle: http://jsfiddle.net/je2rw3rs/

EDYTOWAĆ

Jeśli nie chcesz obserwować każdej zmiany w obiekcie najwyższego poziomu, a po prostu chcesz mniej niezręczną składnię do bezpośredniego oglądania zagnieżdżonych obiektów, możesz po prostu obejrzeć computed:

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

Oto JSFiddle: http://jsfiddle.net/oa07r5fw/

craig_h
źródło
2
w ten sposób „handler” będzie wywoływany za każdym razem, gdy jakikolwiek rekwizyt ma odmianę, moim celem jest oddzielenie handlerów w celu zaobserwowania, czy zmiany zdarzają się w „prop” lub w myArray w „someOtherProp” osobno
Plastik
9
Jeśli chcesz tylko obserwować zmiany w określonych zagnieżdżonych obiektach, to co robisz jest w porządku. Jeśli chcesz mniej niezręcznej składni, możesz computedzamiast tego obejrzeć : jsfiddle.net/c52nda7x
craig_h
Dokładnie to, czego szukałem, teraz wydaje mi się to logiczne: utwórz obliczoną właściwość, która zwróci zagnieżdżony rekwizyt i będzie go obserwować. Wielkie dzięki.
Plastik
Chcę tylko dodać - że druga opcja naprawdę niewiele różni się od oryginalnego obserwatora. Wewnętrznie obliczona właściwość jest tylko obserwatorem wszystkich odnośnych obiektów / wartości w funkcji, która ustawia wartość danych na wynik funkcji. - Więc prawdopodobnie uczynisz kod nieco bardziej skomplikowanym z tym samym efektem.
Falco
22
@Falco Właśnie znalazłem odpowiedź w komentarzu tutaj . peerbolte wskazał, że można to zrobić, umieszczając nazwę zegarka w pojedynczym cudzysłowie. Przetestowałem to i działa. Więc w tym przypadku byłoby to watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} całkiem sprytne. Kocham Vue!
Ron C
436

Kolejne dobre i nieco bardziej eleganckie podejście wygląda następująco:

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

(Nauczyłem się tego podejścia od @peerbolte w komentarzu tutaj )

Ron C.
źródło
47
o wiele bardziej elegancki (nie tylko odrobinę): P. Powinno to znajdować się w dokumentacji Vue.js, ponieważ jest to częsty przypadek użycia. Godzinami szukałem rozwiązania, dziękuję za odpowiedź.
Claudiu
11
@symba Interesujące jest to, że ludzie nie zauważyli, że jest to ta sama rzecz, której OP chciał uniknąć, tylko w ES5składni. Możemy oczywiście argumentować, że ES2015 method definitionsam w sobie nie jest zbyt elegancki, ale w pewnym sensie pomija sens pytania, a mianowicie , jak uniknąć zawijania nazwy, to cytaty. Domyślam się, że większość ludzi zastanawia się nad pierwotnym pytaniem, które już zawiera odpowiedź, i faktycznie szuka czegoś, co, jak mówisz, nie jest zbyt dobrze udokumentowane,
craig_h
1
@craig_h super interesujący punkt. Początkowo polowałem godzinami, aby znaleźć dobry sposób na obserwację zagnieżdżonego rekwizytu. I ten tytuł pytania był najbliższym pytaniem, jakie znalazłem, ale brakowało mi, że umieścił cytowaną rekwizyt w treści pytania, ale odpowiedzi nie były tak eleganckie. W końcu znalazłem cytowane podejście rekwizytowe w komentarzu do innego pytania i kusiło mnie, aby stworzyć pytanie, aby samemu odpowiedzieć na nie jako dokumentację cytowanego podejścia rekwizytowego na SO. Ale to pytanie było dokładnie tym, czym byłby mój tytuł, więc dodałem tutaj odpowiedź. Może powinienem był stworzyć nowe pytanie.
Ron C
9
@RonC To popularne pytanie, a twoja odpowiedź jest dokładnie tym, czego szuka wiele osób, więc myślę, że dobrze tu pasuje. Każdy z nas ma ograniczony czas i większość z nas ledwo czyta pytania, więc z pewnością nie była to krytyka udzielonej odpowiedzi, ale jestem trochę pedantyczny, wskazując, że moja pierwotna odpowiedź była specyficzna dla pytanie i nie było przeznaczone do wyrażenia opinii. Właściwie podoba mi się podejście polegające na „oglądaniu obliczeniowym”, jednak wielu woli mniej skomplikowane podejście „cytatów”, które zwięźle streściłeś w swojej odpowiedzi.
craig_h
10
Oficjalna dokumentacja zawiera teraz to podejście
feihcsim
19

Głęboki wgląd VueJ w obiekty potomne

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

Co zrobić, jeśli chcesz oglądać nieruchomość przez chwilę, a następnie ją cofnąć?

Lub obejrzeć właściwość elementu potomnego biblioteki?

Możesz użyć „obserwatora dynamicznego”:

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

$watchZwraca unwatch funkcję, która będzie oglądać jeśli to się nazywa.

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

Możesz także skorzystać z deepopcji:

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

Proszę zajrzeć do dokumentów

roli roli
źródło
2
Według dokumentów nie należy używać funkcji strzałek do definiowania obserwatora: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

Nie widzę tu wspomnianego, ale można również użyć vue-property-decoratorwzorca, jeśli rozszerzasz swoją Vueklasę.

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

export default class SomeClass extends Vue {
   ...

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

   ...
}
getglad
źródło
5

Innym sposobem na dodanie tego, że „włamałem się” do tego rozwiązania, było zrobienie tego: ustawiłem osobną computedwartość, która po prostu zwróci wartość zagnieżdżonego obiektu.

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
źródło
dobry;) ale co z tablicami? :)
WEBCENTER
3

Mój problem z zaakceptowaną odpowiedzią użycia deep: truepolega na tym, że podczas głębokiego oglądania tablicy nie mogę łatwo zidentyfikować, który element tablicy zawiera zmianę. Jedynym jasnym rozwiązaniem, jakie znalazłem, jest ta odpowiedź, która wyjaśnia, jak utworzyć komponent, abyś mógł oglądać każdy element tablicy osobno.

krubo
źródło
1
Tak, to prawda, ale nie po to jest głęboki obserwator (tak naprawdę znajdziesz oldVal i newValoba odnoszą się do tego samego obiektu, więc są takie same). Intencją a deep watcherjest zrobienie czegoś, gdy zmienią się wartości, na przykład wyemitowanie zdarzenia, wywołanie ajax itp. W przeciwnym razie prawdopodobnie potrzebujesz komponentu, jak wskazano w odpowiedzi Mani.
craig_h
Miałem ten sam problem ze śledzeniem poszczególnych zmian! Znalazłem jednak rozwiązanie i udokumentowałem je tutaj .
Erik Koopmans
3

Śledzenie pojedynczych zmienionych pozycji na liście

Jeśli chcesz oglądać 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, 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});
    });
  },
});

Jeśli lista nie jest wypełniona od razu (jak w pierwotnym pytaniu), możesz przenieść logikę createddo dowolnego miejsca, np. Wewnątrz.then() bloku.

Oglądanie zmieniającej się listy

Jeśli twoja lista sama się aktualizuje, aby mieć nowe lub usunięte elementy, opracowałem przydatny wzór, który „płytki” obserwuje samą listę i dynamicznie obserwuje / odblokowuje elementy, gdy lista się zmienia:

// 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
źródło
0

Żadna odpowiedź nie działała dla mnie. W rzeczywistości, jeśli chcesz oglądać zagnieżdżone dane, a komponenty są wywoływane wiele razy. Są więc nazywani różnymi rekwizytami, aby je zidentyfikować. Na przykład<MyComponent chart="chart1"/> <MyComponent chart="chart2"/> moim obejściem jest utworzenie dodatkowej zmiennej stanu Vuex, którą ręcznie aktualizuję, aby wskazać właściwość, która była ostatnio aktualizowana.

Oto przykład implementacji 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
        },
}

W dowolnej funkcji komponentu, aby zaktualizować sklep:

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

Teraz na dowolnym komponencie, aby uzyskać aktualizację:

    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
źródło