Vue.js - Cara mengawasi data bersarang dengan benar

414

Saya mencoba memahami cara menonton dengan benar untuk beberapa variasi penyangga. Saya memiliki komponen induk (file .vue) yang menerima data dari panggilan ajax, memasukkan data ke dalam objek dan menggunakannya untuk membuat beberapa komponen anak melalui v-for directive, di bawah penyederhanaan implementasi saya:

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

... lalu di dalam <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]);
            }
    });
}

objek barang seperti ini:

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

Sekarang, di dalam komponen "pemain" anak saya, saya mencoba mengawasi variasi properti Item apa pun dan saya menggunakan:

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

Ini bekerja tetapi tampaknya agak rumit bagi saya dan saya bertanya-tanya apakah ini cara yang tepat untuk melakukannya. Tujuan saya adalah melakukan beberapa tindakan setiap kali propberubah atau myArraymendapatkan elemen baru atau variasi di dalamnya. Setiap saran akan dihargai.

Plastik
sumber
9
Cukup gunakan "item.someOtherProp": function (newVal, oldVal){seperti yang disarankan @Ron.
Reiner
1
@Reiner, inilah tepatnya yang ingin dia hindari dalam pertanyaan; hanya saja hal yang sama menggunakan sintaks ES5. Sebenarnya tidak ada overhead tambahan dalam menonton komputasi dan ini adalah teknik yang berguna dalam berbagai situasi.
craig_h
1
Apakah mungkin untuk melihat perubahan di semua keysbagian dalam objek? Contoh:"item.*": function(val){...}
Charlie

Jawaban:

623

Anda dapat menggunakan pengamat dalam untuk itu:

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

Ini sekarang akan mendeteksi setiap perubahan pada objek dalam itemarray dan penambahan ke array itu sendiri (bila digunakan dengan Vue.set ). Inilah JSFiddle: http://jsfiddle.net/je2rw3rs/

EDIT

Jika Anda tidak ingin melihat setiap perubahan pada objek tingkat atas, dan hanya ingin sintaks yang kurang canggung untuk menonton objek bersarang secara langsung, Anda dapat menonton computedsaja:

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

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

craig_h
sumber
2
dengan cara ini "penangan" akan dipanggil setiap kali prop memiliki variasi, tujuan saya adalah untuk memisahkan penangan untuk mengamati apakah perubahan dengan senang hati untuk "menopang" atau dalam myArray dalam "someOtherProp" secara terpisah
Plastik
9
Jika Anda hanya ingin melihat perubahan pada objek bersarang tertentu maka apa yang Anda lakukan baik-baik saja. Jika Anda menginginkan sintaks yang kurang canggung, Anda dapat menontonnya sebagai computedgantinya: jsfiddle.net/c52nda7x
craig_h
Persis apa yang saya cari, sekarang rasanya sangat logis bagi saya: buat properti yang dikomputasi yang mengembalikan prop bersarang dan melihatnya. Tnx banyak.
Plastik
Saya hanya ingin menambahkan - bahwa opsi kedua benar-benar tidak jauh berbeda dari pengamat asli. Secara internal, properti yang dihitung hanyalah pengamat pada semua objek / nilai yang direferensikan dalam fungsi yang menetapkan nilai-data ke hasil fungsi. - Jadi, Anda mungkin hanya membuat kode sedikit lebih berbelit-belit dengan efek yang sama.
Falco
22
@ Falco Saya baru saja menemukan jawabannya di komentar di sini . peerbolte menunjukkan bahwa hal itu dapat dilakukan dengan memasukkan nama arloji dalam tanda kutip tunggal. Saya telah menguji ini dan berhasil. Jadi dalam hal ini akan sangat watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} cantik. Saya suka Vue!
Ron C
436

Pendekatan lain yang bagus dan yang sedikit lebih elegan adalah sebagai berikut:

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

(Saya mempelajari pendekatan ini dari @peerbolte dalam komentar di sini )

Ron C
sumber
47
cara yang lebih elegan (tidak hanya sedikit): P. Ini harus dalam dokumentasi Vue.js karena ini adalah kasus penggunaan umum. Saya sudah mencari berjam-jam untuk sebuah solusi, terima kasih atas jawaban Anda.
Claudiu
11
@ symba Sangat menarik bahwa orang-orang tidak memperhatikan bahwa ini adalah hal yang sama yang diminta OP untuk dihindari, hanya dalam ES5sintaksis. Kita tentu saja dapat berargumen bahwa ES2015 method definitionitu sendiri tidak terlalu elegan, tetapi agak merindukan inti dari pertanyaan, yaitu bagaimana menghindari pembungkus nama adalah kutipan. Saya kira kebanyakan orang mengabaikan pertanyaan asli, yang sudah berisi jawabannya, dan sebenarnya mencari sesuatu yang, seperti yang Anda katakan, tidak didokumentasikan dengan sangat baik,
craig_h
1
@craig_h poin yang sangat menarik. Saya awalnya berburu berjam-jam untuk menemukan cara yang baik untuk menonton prop bersarang. Dan judul pertanyaan ini adalah pertanyaan terdekat yang saya temukan tetapi saya melewatkan bahwa dia mendaftar prop yang dikutip di badan pertanyaan tetapi menemukan jawabannya tidak begitu elegan. Saya akhirnya menemukan pendekatan prop yang dikutip dalam komentar dari pertanyaan lain dan tergoda untuk membuat pertanyaan hanya untuk menjawab sendiri sebagai dokumentasi dari pendekatan prop yang dikutip pada SO. Tapi pertanyaan ini persis seperti apa judul saya jadi saya menambahkan jawabannya di sini. Mungkin saya harus membuat pertanyaan baru.
Ron C
9
@RonC Ini adalah pertanyaan populer dan jawaban Anda persis seperti apa yang dicari banyak orang, jadi saya rasa ini baik-baik saja di sini. Kita semua hanya memiliki waktu terbatas, dan sebagian besar dari kita hampir tidak membaca pertanyaan, jadi ini tentu saja bukan kritik atas jawaban yang Anda berikan, saya hanya sedikit pedantic dalam menunjukkan bahwa jawaban asli saya khusus untuk pertanyaan dan tidak dimaksudkan untuk dikemukakan pendapat. Saya sebenarnya menyukai pendekatan "menonton yang dihitung", namun, banyak yang lebih menyukai pendekatan "kutipan" yang tidak terlalu berbelit-belit, yang telah Anda ringkaskan dalam jawaban Anda.
craig_h
10
The dokumentasi resmi termasuk pendekatan ini sekarang
feihcsim
19

VueJs sangat memperhatikan objek anak-anak

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

Bagaimana jika Anda ingin menonton properti untuk sementara waktu dan kemudian membatalkannya?

Atau untuk menonton properti komponen anak pustaka?

Anda dapat menggunakan "pengamat dinamis":

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

The $watchmengembalikan fungsi unwatch yang akan berhenti menonton jika disebut.

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

Anda juga dapat menggunakan deepopsi:

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

Pastikan untuk melihat ke dokumen

roli roli
sumber
2
Menurut dokumen , Anda tidak boleh menggunakan fungsi panah untuk mendefinisikan pengamat: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

Tidak melihatnya disebutkan di sini, tetapi juga memungkinkan untuk menggunakan vue-property-decoratorpola jika Anda memperluas Vuekelas Anda .

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

export default class SomeClass extends Vue {
   ...

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

   ...
}
getglad
sumber
5

Cara lain untuk menambahkan yang saya gunakan untuk 'meretas' solusi ini adalah dengan melakukan ini: Saya mengatur computednilai terpisah yang hanya akan mengembalikan nilai objek bersarang.

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
sumber
bagus;) tetapi bagaimana dengan array? :)
WEBCENTER
3

Masalah saya dengan jawaban yang diterima dari menggunakan deep: true, adalah bahwa ketika deep-menonton sebuah array, saya tidak dapat dengan mudah mengidentifikasi mana elemen array berisi perubahan. Satu-satunya solusi jelas yang saya temukan adalah jawaban ini, yang menjelaskan cara membuat komponen sehingga Anda dapat menonton setiap elemen array secara terpisah.

Krubo
sumber
1
Ya itu benar, tapi bukan untuk itu pengamat yang dalam (pada kenyataannya Anda akan menemukan oldValdan newValkeduanya merujuk objek yang sama, jadi sama saja). Maksud dari a deep watcheradalah untuk melakukan sesuatu ketika nilai berubah, seperti memancarkan suatu peristiwa, membuat panggilan ajax dll. Jika tidak, Anda mungkin menginginkan komponen, seperti yang ditunjukkan dalam jawaban Mani.
craig_h
Saya memiliki masalah yang sama untuk melacak perubahan individu! Saya menemukan solusi, dan mendokumentasikannya di sini .
Erik Koopmans
3

Melacak setiap item yang diubah dalam daftar

Jika Anda ingin menonton semua item dalam daftar dan mengetahui item mana dalam daftar yang diubah, Anda dapat mengatur pengamat khusus pada setiap item secara terpisah, seperti:

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

Jika daftar Anda tidak langsung dihuni (seperti dalam pertanyaan asli), Anda dapat memindahkan logika keluar createdke mana pun dibutuhkan, misalnya di dalam.then() blok.

Menonton daftar yang berubah

Jika daftar Anda sendiri diperbarui untuk memiliki item baru atau dihapus, saya telah mengembangkan pola yang berguna yang "dangkal" menonton daftar itu sendiri, dan secara dinamis menonton / membuka item ketika daftar berubah:

// 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
sumber
0

Tidak ada jawaban untuk saya yang berfungsi. Sebenarnya jika Anda ingin menonton data bersarang dengan Komponen yang dipanggil beberapa kali. Jadi mereka dipanggil dengan alat peraga yang berbeda untuk mengidentifikasi mereka. Sebagai contoh, <MyComponent chart="chart1"/> <MyComponent chart="chart2"/> solusi saya adalah membuat variabel status vuex addionnal, yang saya perbarui secara manual untuk menunjuk ke properti yang terakhir diperbarui.

Berikut ini adalah contoh implementasi 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
        },
}

Pada fungsi Komponen apa pun untuk memperbarui toko:

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

Sekarang pada Komponen apa saja untuk mendapatkan pembaruan:

    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
sumber