Vue-オブジェクトの配列を詳細に監視し、変更を計算していますか?


108

people次のようなオブジェクトを含むと呼ばれる配列があります。

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

それは変わることができます:

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

フランクが33歳になったことに注目してください。

people配列を監視しようとするアプリがあり、値のいずれかが変更されると、変更をログに記録します。

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

私はこれを、昨日アレイの比較について尋ね質問に基づいて、最も迅速に機能する回答を選択しました。

したがって、この時点で、次の結果が表示されることを期待しています。 { id: 1, name: 'Frank', age: 33 }

しかし、コンソールに戻るのは次のとおりです(コンポーネントで使用したことを念頭に置いてください)。

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

そして、私が作成したコードペンでは、結果は空の配列であり、変更された変更されたオブジェクトではなく、私が期待したものになります。

誰かがこれがなぜ起こっているのか、または私がここで間違っているところを示唆できるなら、それは大いに感謝します、ありがとう!

回答:


136

古い値と新しい値の比較関数に問題があります。後でデバッグ作業が増えるので、それほど複雑にしない方がよいでしょう。あなたはそれをシンプルに保つべきです。

最善の方法はperson-component、以下に示すように、独自のコンポーネント内で個別にを作成してすべての人を監視することです。

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

内部のpersonコンポーネントを監視するための実用的な例を以下に示します。親側で処理する場合は、を使用$emitしてid、変更された人物のを含むイベントを上向きに送信できます。

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>


それは確かに実用的なソリューションですが、私のユースケースに完全に一致しているわけではありません。ご覧のとおり、実際にはアプリと1つのコンポーネントがあります。コンポーネントはvue-materialテーブルを使用し、値をインラインで編集する機能を備えたデータを一覧表示します。値の1つを変更してから、何が変更されたかを確認しようとしています。この場合、実際には、前後の配列を比較して、どのような違いがあるかを確認します。問題を解決するためのソリューションを実装できますか?実際、私はそうすることができたかもしれませんが、それがvue-material内でこの点に関して利用可能なものの流れに逆らっていると感じるだけです
Craig van Tonder

2
ちなみに、これを説明してくれてありがとう、私はVueについてもっと学ぶのに役立ちました。
Craig van Tonder

これを理解するのに少し時間がかかりましたが、あなたは完全に正しいです。これは魅力のように機能し、混乱やさらなる問題を回避したい場合に適切な方法です:)
Craig van Tonder

1
私もこれに気づき、同じことを考えましたが、オブジェクトに含まれているのは、値を含む値インデックスです。ゲッターとセッターはそこにありますが、比較すると無視されます。理解が不十分であるため、そうだと思いますプロトタイプでは評価しません。他の回答の1つは、機能しない理由を提供します。それは、newValとoldValが同じものだったためです。少し複雑ですが、いくつかの場所で対処されているものですが、別の回答は、簡単に作成できる適切な回避策を提供します比較のための不変オブジェクト。
Craig van Tonder 2016

1
ただし、最終的には、一目で理解しやすくなり、値が変化したときに何が利用できるかという点で柔軟性が高まります。これは、Vueでシンプルに保つことの利点を理解するのに大いに役立ちましたが、他の質問で見たように、少し行き詰まりました。どうもありがとう!:)
Craig van Tonder 2016

21

問題を解決するために実装を変更しました。古い変更を追跡するオブジェクトを作成し、それと比較しました。あなたはそれをあなたの問題を解決するために使うことができます。

ここでは、古い値を別の変数に格納し、それをウォッチで使用するメソッドを作成しました。

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

更新された コードペンを見る


マウントされたら、データのコピーを保存し、これを使用してデータと比較します。興味深いですが、私のユースケースはより複雑になり、オブジェクトを配列に追加したり配列から削除したりするときにどのように機能するかわかりません。@ Quirkは問題を解決するための適切なリンクも提供しました。しかし、あなたがを使用できることを知りませんでしたvm.$data、ありがとう!
Craig van Tonder

はい、そして私はまた、メソッドを再度呼び出すことによって、時計の後でそれを更新しています。それによって、元の値に戻った場合は、変更も追跡されます。
Viplock

ああ、私はそこに隠すことは意味がなく、これに対処するための複雑な方法ではないことに気づきませんでした(githubのソリューションとは対照的です)。
Craig van Tonder

元の配列に何かを追加したり削除したりする場合は、もう一度メソッドを呼び出してください。そうすれば、解決策をもう一度試すことができます。
Viplock

1
私の場合、_。cloneDeep()は本当に役に立ちました。ありがとうございました!!本当に役に立ちました!
Cristiana Pereira

18

これは明確に定義された動作です。あなたは、古い値を得ることができない変異したオブジェクトを。との両方が同じオブジェクトnewValoldVal参照しているためです。Vueは、変更したオブジェクトの古いコピーを保持しません

あなたが持っていた置き換え、別の1を持つオブジェクトを、Vueが正しいリファレンスをご提供しています。

ドキュメントNoteセクションをお読みください。(vm.$watch

これについてはこちらこちらをご覧ください


3
ああ私の帽子、どうもありがとう!それはトリッキーなものです... valとoldValが異なることが完全に予想されましたが、それらを検査した後、新しい配列の2つのコピーであることがわかり、以前は追跡していません。より多くのビットをリードし、同じ誤解について、この未回答のSOの質問が見つかりました:stackoverflow.com/questions/35991494/...を
クレイグ・バンTonderの

5

これは、オブジェクトを深く監視するために使用するものです。私の要件は、オブジェクトの子フィールドを監視することでした。

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

stackoverflow.com/a/41136186/2110294に記載されている洞窟についての理解が欠けていると思います。明確にするために、これは問題の解決策ではなく、特定の状況で期待したように機能しません。
Craig van Tonder

これはまさに私が探していたものです。ありがとう
Jaydeep Shil

ここでも同じです。まさに私が必要としたものです!ありがとう。
ガンター

4

コンポーネントソリューションとディープクローンソリューションには利点がありますが、問題もあります。

  1. 抽象データの変更を追跡したい場合があります。そのデータの周りにコンポーネントを構築することが常に意味があるとは限りません。

  2. 変更を加えるたびにデータ構造全体をディープクローニングすると、非常にコストがかかる可能性があります。

もっと良い方法があると思います。リスト内のすべてのアイテムを監視し、リスト内のどのアイテムが変更されたか知りたい場合は、次のように、すべてのアイテムに個別にカスタムウォッチャーを設定できます。

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

この構造でhandleChange()は、変更された特定のリストアイテムを受け取ります。そこから、好きな処理を行うことができます。

また、リストに項目を追加または削除する場合(既にそこにある項目を操作するだけではない)の場合に備えて、ここより複雑なシナリオを文書化しました。


エリックに感謝します。有効なポイントを提示してください。提供された方法論は、質問の解決策として実装された場合に最も確実に役立ちます。
Craig van Tonder
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.