简体   繁体   English

Vue.js - 如何正确监视嵌套数据

[英]Vue.js - How to properly watch for nested data

I'm trying to understand how to properly watch for some prop variation.我试图了解如何正确地观察一些道具变化。 I have a parent component (.vue files) that receive data from an ajax call, put the data inside an object and use it to render some child component through a v-for directive, below a simplification of my implementation:我有一个从 ajax 调用接收数据的父组件(.vue 文件),将数据放入 object 并使用它通过 v-for 指令呈现一些子组件,下面是我的实现的简化:

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

... then inside <script> tag: ... 然后在<script>标签内:

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

item objects are like this:项目对象是这样的:

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

Now, inside my child "player" component I'm trying to watch for any Item's property variation and I use:现在,在我的子“玩家”组件中,我试图观察任何项目的属性变化,我使用:

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

It works but it seems a bit tricky to me and I was wondering if this is the right way to do it.它有效,但对我来说似乎有点棘手,我想知道这是否是正确的方法。 My goal is to perform some action every time prop changes or myArray gets new elements or some variation inside existing ones.我的目标是在每次prop更改或myArray获取新元素或现有元素中的某些变化时执行一些操作。 Any suggestion will be appreciated.任何建议将不胜感激。

You can use a deep watcher for that:您可以为此使用深度观察者

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

This will now detect any changes to the objects in the item array and additions to the array itself (when used with Vue.set ).现在,这将检测item数组中对象的任何更改以及数组本身的添加(当与Vue.set一起使用时)。 Here's a JSFiddle: http://jsfiddle.net/je2rw3rs/这是一个 JSFiddle:http: //jsfiddle.net/je2rw3rs/

EDIT编辑

If you don't want to watch for every change on the top level object, and just want a less awkward syntax for watching nested objects directly, you can simply watch a computed instead:如果你不想观察顶层对象的每一个变化,而只是想要一种不那么尴尬的语法来直接观察嵌套对象,你可以简单地观察一个computed对象:

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

Here's the JSFiddle: http://jsfiddle.net/oa07r5fw/这是 JSFiddle:http: //jsfiddle.net/oa07r5fw/

Another good approach and one that is a bit more elegant is as follows:另一种好方法和更优雅的方法如下:

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

(I learned this approach from @peerbolte in the comment here ) (我在这里的评论中从@peerbolte 学到了这种方法)

VueJs deep watch in child objects VueJs 深入观察子对象

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

Personally I prefer this clean implementation:我个人更喜欢这种干净的实现:

watch: {
  myVariable: {
     handler(newVal, oldVal){  // here having access to the new and old value
       // do stuff
     },
     deep: true,
     immediate: true //  Also very important the immediate in case you need it, the callback will be called immediately after the start of the observation

  }
}

How if you want to watch a property for a while and then to un-watch it?如果您想看一段时间然后不看它怎么办?

Or to watch a library child component property?或者看一个库子组件的属性?

You can use the "dynamic watcher":您可以使用“动态观察者”:

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

The $watch returns an unwatch function which will stop watching if it is called. $watch返回一个 unwatch 函数,如果它被调用,它将停止监视。

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

Also you can use the deep option:您也可以使用deep选项:

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

Please make sure to take a look to docs请务必查看文档

Another way to add that I used to 'hack' this solution was to do this: I set up a seperate computed value that would simply return the nested object value.另一种添加我用来“破解”此解决方案的方法是这样做:我设置了一个单独的computed值,该值将简单地返回嵌套对象值。

data : function(){
    return {
        countries : {
            UnitedStates : {
                value: "hello world";
            }.
        },
    };
},
computed : {
    helperName : function(){
        return this.countries.UnitedStates.value;
    },
},
watch : {
    helperName : function(newVal, oldVal){
        // do this...
    }
}

Tracking individual changed items in a list跟踪列表中的单个更改项

If you want to watch all items in a list and know which item in the list changed, you can set up custom watchers on every item separately, like so:如果您想查看列表中的所有项目并知道列表中的哪个项目发生了变化,您可以分别在每个项目上设置自定义观察者,如下所示:

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

If your list isn't populated straight away (like in the original question), you can move the logic out of created to wherever needed, eg inside the .then() block.如果您的列表没有立即填充(就像在原始问题中一样),您可以将逻辑从created移到任何需要的地方,例如在.then()块内。

Watching a changing list观看不断变化的列表

If your list itself updates to have new or removed items, I've developed a useful pattern that "shallow" watches the list itself, and dynamically watches/unwatches items as the list changes:如果您的列表本身更新为有新的或删除的项目,我开发了一个有用的模式,它“浅”地监视列表本身,并随着列表的变化动态地监视/取消监视项目:

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

Not seeing it mentioned here, but also possible to use the vue-property-decorator pattern if you are extending your Vue class.这里没有提到它,但如果你正在扩展你的Vue类,也可以使用vue-property-decorator模式。

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

export default class SomeClass extends Vue {
   ...

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

   ...
}

I've found it works this way too:我发现它也可以这样工作:

watch: {
    "details.position"(newValue, oldValue) {
        console.log("changes here")
    }
},
data() {
    return {
      details: {
          position: ""
      }
    }
}

My problem with the accepted answer of using deep: true , is that when deep-watching an array, I can't easily identify which element of the array contains the change.我对使用deep: true的公认答案的问题是,在深入观察数组时,我无法轻松识别数组的哪个元素包含更改。 The only clear solution I've found is this answer, which explains how to make a component so you can watch each array element individually.我找到的唯一明确的解决方案是这个答案,它解释了如何制作一个组件,以便您可以单独查看每个数组元素。

None of the answer for me was working.对我来说没有一个答案是有效的。 Actually if you want to watch on nested data with Components being called multiple times.实际上,如果您想通过多次调用组件来查看嵌套数据。 So they are called with different props to identify them.所以他们被称为不同的道具来识别它们。 For example <MyComponent chart="chart1"/> <MyComponent chart="chart2"/> My workaround is to create an addionnal vuex state variable, that I manually update to point to the property that was last updated.例如<MyComponent chart="chart1"/> <MyComponent chart="chart2"/>我的解决方法是创建一个附加的 vuex 状态变量,我手动更新它以指向上次更新的属性。

Here is a Vuex.ts implementation example:这是一个 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
        },
}

On any Component function to update the store:在任何更新商店的组件功能上:

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

Now on any Component to get the update:现在在任何组件上获取更新:

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

For anyone looking for Vue 3对于任何正在寻找 Vue 3 的人


import { watch } from 'vue';

...
...

watch(
  () => yourNestedObject,              // first param, your object
  (currValue, prevValue) => {          // second param, watcher callback
    console.log(currValue, prevValue);
  },
  { deep: true }                       // third param, for deep checking
);

You can refer to the documentation here: https://v3.vuejs.org/guide/reactivity-computed-watchers.html#watch你可以参考这里的文档: https ://v3.vuejs.org/guide/reactivity-computed-watchers.html#watch

I used deep:true, but found the old and new value in the watched function was the same always.我使用了 deep:true,但发现 watch 函数中的新旧值始终相同。 As an alternative to previous solutions I tried this, which will check any change in the whole object by transforming it to a string:作为以前解决方案的替代方案,我尝试了这个,它将通过将整个对象转换为字符串来检查整个对象的任何变化:

created() {
    this.$watch(
        () => JSON.stringify(this.object),
            (newValue, oldValue) => {
                //do your stuff                
            }
    );
},

Here's a way to write watchers for nested properties:这是一种为嵌套属性编写观察器的方法:

    new Vue({
        ...allYourOtherStuff,
        watch: {
            ['foo.bar'](newValue, oldValue) {
                // Do stuff here
            }
        }
    });

You can even use this syntax for asynchronous watchers:您甚至可以将此语法用于异步观察者:

    new Vue({
        ...allYourOtherStuff,
        watch: {
            async ['foo.bar'](newValue, oldValue) {
                // Do stuff here
            }
        }
    });

https://vuejs.org/guide/essentials/watchers.html#deep-watchers https://vuejs.org/guide/essentials/watchers.html#deep-watchers

export default {
  watch: {
    someObject: {
      handler(newValue, oldValue) {
        // Note: `newValue` will be equal to `oldValue` here
        // on nested mutations as long as the object itself
        // hasn't been replaced.
      },
      deep: true
    }
  }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM