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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/42133894/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 00:56:29  来源:igfitidea点击:

Vue.js - How to properly watch for nested data

javascriptvue.jsvue-componentvue-resource

提问by Plastic

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:

我试图了解如何正确观察一些道具变化。我有一个父组件(.vue 文件),它从 ajax 调用接收数据,将数据放在一个对象中,并使用它通过 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:

现在,在我的子“播放器”组件中,我试图观察任何 Item 的属性变化,我使用:

...
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 propchanges or myArraygets new elements or some variation inside existing ones. Any suggestion will be appreciated.

它有效,但对我来说似乎有点棘手,我想知道这是否是正确的方法。我的目标是每次prop更改或myArray获取新元素或现有元素中的某些变化时执行一些操作。任何建议将不胜感激。

回答by craig_h

You can use a deep watcherfor that:

您可以为此使用深度观察者

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

This will now detect any changes to the objects in the itemarray and additions to the array itself (when used with Vue.set). Here's a JSFiddle: http://jsfiddle.net/je2rw3rs/

这现在将检测item数组中对象的任何更改以及对数组本身的添加(与Vue.set 一起使用时)。这是一个 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 computedinstead:

如果您不想监视顶级对象上的每个更改,而只想使用一种不那么笨拙的语法来直接监视嵌套对象,则可以简单地观察一个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/

回答by Ron C

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 那里学到了这种方法)

回答by Alper Ebicoglu

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

回答by roli roli

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 $watchreturns an unwatch function which will stop watching if it is called.

$watch返回的取消监视功能,这将驻足观望,如果它被调用。

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

Also you can use the deepoption:

您也可以使用该deep选项:

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

Please make sure to take a look to docs

请务必查看文档

回答by getglad

Not seeing it mentioned here, but also possible to use the vue-property-decoratorpattern if you are extending your Vueclass.

没有看到这里提到它,但vue-property-decorator如果您要扩展您的Vue课程,也可以使用该模式。

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

export default class SomeClass extends Vue {
   ...

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

   ...
}

回答by Zac

Another way to add that I used to 'hack' this solution was to do this: I set up a seperate computedvalue that would simply return the nested object value.

另一种添加我用来“破解”此解决方案的方法是这样做:我设置了一个单独的computed值,该值将简单地返回嵌套的对象值。

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

回答by Erik Koopmans

Tracking individual changed items in a list

跟踪列表中的单个更改项

If you want to watch all items in a list and know whichitem 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 createdto wherever needed, e.g. 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();
  },
});

回答by krubo

My problem with the accepted answer of using deep: true, is that when deep-watching an array, I can't easily identify whichelement of the array contains the change. 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.

我对 using 的公认答案的问题deep: true是,在深入观察数组时,我无法轻松识别数组的哪个元素包含更改。我找到的唯一明确的解决方案是这个答案,它解释了如何制作组件,以便您可以单独查看每个数组元素。

回答by thomasL

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:

在任何用于更新商店的 Component 函数上:

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