Vue - Mendalam mengamati berbagai objek dan menghitung perubahannya?

108

Saya memiliki sebuah array bernama peopleyang berisi objek sebagai berikut:

Sebelum

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

Itu bisa berubah:

Setelah

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

Perhatikan bahwa Frank baru saja berusia 33 tahun.

Saya memiliki aplikasi di mana saya mencoba untuk menonton array orang dan ketika salah satu nilai berubah, maka catat perubahannya:

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

Saya mendasarkan ini pada pertanyaan yang saya tanyakan kemarin tentang perbandingan array dan memilih jawaban kerja tercepat.

Jadi, pada titik ini saya berharap untuk melihat hasil dari: { id: 1, name: 'Frank', age: 33 }

Tetapi yang saya dapatkan kembali di konsol adalah (mengingat saya memilikinya di sebuah komponen):

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

Dan pada codepen yang saya buat , hasilnya berupa array kosong dan bukan objek yang diubah yang berubah seperti yang saya harapkan.

Jika ada yang bisa menyarankan mengapa ini terjadi atau di mana saya salah di sini maka itu akan sangat dihargai, terima kasih banyak!

Craig van Tonder
sumber

Jawaban:

136

Fungsi perbandingan Anda antara nilai lama dan nilai baru mengalami beberapa masalah. Lebih baik tidak mempersulit banyak hal, karena ini akan meningkatkan upaya debugging Anda nanti. Anda harus membuatnya tetap sederhana.

Cara terbaik adalah membuat person-componentdan menonton setiap orang secara terpisah di dalam komponennya sendiri, seperti yang ditunjukkan di bawah ini:

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

Silakan temukan di bawah ini contoh kerja untuk menonton komponen orang dalam. Jika Anda ingin menanganinya di sisi orang tua, Anda dapat menggunakan $emituntuk mengirim acara ke atas, berisi iddari orang yang diubah.

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
sumber
Itu memang solusi yang berfungsi tetapi tidak sepenuhnya sesuai dengan kasus penggunaan saya. Anda lihat, sebenarnya saya memiliki aplikasi dan satu komponen, komponen tersebut menggunakan tabel vue-material dan mencantumkan data dengan kemampuan untuk mengedit nilai secara in-line. Saya mencoba untuk mengubah salah satu nilai kemudian memeriksa apa yang berubah jadi dalam kasus ini, sebenarnya membandingkan array sebelum dan sesudah untuk melihat perbedaan apa yang ada. Bisakah saya menerapkan solusi Anda untuk menyelesaikan masalah? Memang saya mungkin bisa melakukannya tetapi hanya merasa bahwa itu akan bekerja melawan aliran dari apa yang tersedia dalam hal ini dalam vue-material
Craig van Tonder
2
Ngomong-ngomong, terima kasih telah meluangkan waktu untuk menjelaskan hal ini, ini membantu saya untuk mempelajari lebih lanjut tentang Vue yang sangat saya hargai!
Craig van Tonder
Butuh beberapa saat bagi saya untuk memahami ini tetapi Anda benar sekali, ini berfungsi seperti pesona dan merupakan cara yang benar untuk melakukan sesuatu jika Anda ingin menghindari kebingungan dan masalah lebih lanjut :)
Craig van Tonder
1
Saya juga memperhatikan ini dan memiliki pemikiran yang sama tetapi yang juga terkandung dalam objek adalah indeks nilai yang berisi nilai, pengambil dan penyetel ada di sana tetapi sebagai perbandingan ia mengabaikannya, karena kurangnya pemahaman yang lebih baik menurut saya. tidak mengevaluasi prototipe apa pun. Salah satu jawaban lain memberikan alasan mengapa itu tidak akan berfungsi, itu karena newVal dan oldVal adalah hal yang sama, ini agak rumit tetapi merupakan sesuatu yang telah dibahas di beberapa tempat, namun jawaban lain memberikan solusi yang layak untuk memudahkan pembuatan objek yang tidak dapat diubah untuk tujuan perbandingan.
Craig van Tonder
1
Namun pada akhirnya, cara Anda lebih mudah dipahami secara sekilas dan memberikan lebih banyak fleksibilitas dalam hal apa yang tersedia saat nilainya berubah. Ini sangat membantu saya untuk memahami manfaat membuatnya tetap sederhana di Vue tetapi sedikit terjebak dengannya seperti yang Anda lihat di pertanyaan saya yang lain. Terimakasih banyak! :)
Craig van Tonder
21

Saya telah mengubah penerapannya untuk menyelesaikan masalah Anda, saya membuat objek untuk melacak perubahan lama dan membandingkannya dengan itu. Anda dapat menggunakannya untuk menyelesaikan masalah Anda.

Di sini saya membuat metode, di mana nilai lama akan disimpan dalam variabel terpisah dan, yang kemudian akan digunakan di jam tangan.

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

Lihat codepen yang diperbarui

Viplock
sumber
Jadi ketika sudah terpasang simpan salinan data dan gunakan ini untuk membandingkannya. Menarik, tetapi kasus penggunaan saya akan lebih kompleks dan saya tidak yakin bagaimana cara kerjanya saat menambahkan dan menghapus objek dari array, @Quirk menyediakan tautan yang bagus untuk menyelesaikan masalah juga. Tetapi saya tidak tahu bahwa Anda dapat menggunakan vm.$data, terima kasih!
Craig van Tonder
ya, dan saya memperbaruinya setelah jam tangan juga dengan memanggil metode lagi, dengan itu jika Anda kembali ke nilai aslinya, maka itu juga akan melacak perubahan.
Viplock
Ohhh, saya tidak memperhatikan bahwa bersembunyi di sana, masuk akal dan merupakan cara yang tidak terlalu rumit untuk menangani ini (sebagai lawan dari solusi di github).
Craig van Tonder
dan ya, jika Anda menambahkan atau menghapus sesuatu dari larik asli, cukup panggil kembali metode tersebut dan Anda siap menggunakan solusinya lagi.
Viplock
1
_.cloneDeep () sangat membantu dalam kasus saya. Terima kasih!! Sangat membantu!
Cristiana Pereira
18

Ini adalah perilaku yang terdefinisi dengan baik. Anda tidak bisa mendapatkan nilai lama untuk objek yang bermutasi . Itu karena kedua newValdan oldValmerujuk pada objek yang sama. Vue tidak akan menyimpan salinan lama dari objek yang Anda mutasi.

Seandainya Anda mengganti objek itu dengan yang lain, Vue akan memberi Anda referensi yang benar.

Baca Notebagian di dokumen. ( vm.$watch)

Lebih lanjut tentang ini di sini dan di sini .

Quirk
sumber
3
Oh topiku, Terima kasih banyak! Itu rumit ... Saya benar-benar mengharapkan val dan oldVal berbeda tetapi setelah memeriksanya, saya melihat itu adalah dua salinan dari array baru, itu tidak melacaknya sebelumnya. Baca lebih lanjut dan temukan pertanyaan SO yang tidak terjawab ini terkait kesalahpahaman yang sama: stackoverflow.com/questions/35991494/…
Craig van Tonder
5

Inilah yang saya gunakan untuk mengamati sebuah objek. Persyaratan saya adalah memperhatikan bidang anak objek.

new Vue({
    el: "#myElement",
    data:{
        entity: {
            properties: []
        }
    },
    watch:{
        'entity.properties': {
            handler: function (after, before) {
                // Changes detected.    
            },
            deep: true
        }
    }
});
Alper Ebicoglu
sumber
Saya yakin Anda mungkin kehilangan pemahaman tentang cavet yang dijelaskan di stackoverflow.com/a/41136186/2110294 . Hanya untuk memperjelas, ini bukan solusi untuk pertanyaan dan tidak akan berfungsi seperti yang Anda harapkan dalam situasi tertentu.
Craig van Tonder
inilah yang sebenarnya saya cari !. Terima kasih
Jaydeep Shil
Hal yang sama di sini, persis seperti yang saya butuhkan !! Terima kasih.
Guntar
4

Solusi komponen dan solusi deep-clone memiliki kelebihan, tetapi juga memiliki masalah:

  1. Terkadang Anda ingin melacak perubahan dalam data abstrak - membuat komponen di sekitar data itu tidak selalu masuk akal.

  2. Mengkloning seluruh struktur data Anda setiap kali Anda membuat perubahan bisa sangat mahal.

Saya pikir ada cara yang lebih baik. Jika Anda ingin menonton semua item dalam daftar dan mengetahui item mana dalam daftar yang berubah, 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) {
      // Handle changes here!
      console.log(newVal);
    },
  },
  created () {
    this.list.forEach((val) => {
      this.$watch(() => val, this.handleChange, {deep: true});
    });
  },
});

Dengan struktur ini, handleChange()akan menerima item daftar tertentu yang diubah - dari sana Anda dapat melakukan penanganan yang Anda suka.

Saya juga telah mendokumentasikan skenario yang lebih kompleks di sini , jika Anda menambahkan / menghapus item ke daftar Anda (daripada hanya memanipulasi item yang sudah ada).

Erik Koopmans
sumber
Terima kasih Erik, Anda menyajikan poin yang valid dan metodologi yang diberikan sangat berguna jika diterapkan sebagai solusi untuk pertanyaan tersebut.
Craig van Tonder