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


413

我正在尝试了解如何正确观察道具的一些变化。我有一个父组件(.vue文件),该组件从ajax调用接收数据,将数据放入对象中,并通过v-for指令使用它来呈现某些子组件,以下是我的实现的简化:

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

...然后在<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:{
   prop: value,
   someOtherProp: {
       nestedProp: nestedValue,
       myArray: [{type: "a", num: 1},{type: "b" num: 6} ...]
    },
}

现在,在我的孩子“玩家”组件中,我试图观察任何Item的属性变化,并使用:

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

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


9
只需"item.someOtherProp": function (newVal, oldVal){按@Ron建议使用 即可。
Reiner

1
@Reiner这正是他在问题中要避免的事情;使用ES5语法也是如此。观看计算实际上没有任何额外的开销,并且在各种情况下都是有用的技术。
craig_h 17-10-19

1
是否可以观察keys对象内部的所有变化?示例:"item.*": function(val){...}
查理(Charlie)

Answers:


622

您可以为此使用深度监视程序:

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

现在,它将检测对item数组中对象的任何更改以及对数组本身的添加(与Vue.set一起使用时)。这是一个JSFiddle:http : //jsfiddle.net/je2rw3rs/

编辑

如果您不想监视顶级对象的每项更改,而只想使用一种不太尴尬的语法直接监视嵌套对象,则可以直接观察一个computed

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

这是JSFiddle:http//jsfiddle.net/oa07r5fw/


2
这样,只要任何道具发生变化,都将调用“处理程序”,我的目的是分离处理程序,以便观察是“ prop”还是在“ someOtherProp”中的myArray中发生了更改
Plastic,

9
如果您只想监视特定嵌套对象的更改,那么您正在做的事情就很好了。如果您想要一个不太尴尬的语法,可以computed改用以下方法:jsfiddle.net/c52nda7x
craig_h

正是我要找的东西,现在对我来说似乎很合逻辑:创建一个计算属性,该属性返回嵌套的prop并对其进行监视。Tnx很多。
Plastic

我只想补充一下-第二个选项与原始观察者确实没有太大区别。在内部,计算属性只是函数中所有引用的对象/值的观察者,该值将数据值设置为函数的结果。-因此,您可能只会使代码更加复杂,但效果相同。
Falco

22
@Falco我刚刚在这里评论中找到了答案。peerbolte表示,可以通过将手表名称放在单引号中来完成此操作。我已经对此进行了测试,并且可以正常工作。因此,在这种情况下,它会很watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} 漂亮。我爱Vue!
罗恩C

436

另一种好的方法是一种更优雅的方法,如下所示:

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

(我在这里评论中从@peerbolte学习了这种方法)


47
方式更优雅(不只是一点点):P。这是Vue.js文档中的常见用例。我一直在寻找解决方案的时间,感谢您的回答。
Claudiu

11
@symba有趣的是,人们没有注意到这只是OP要求避免的ES5语法。我们当然可以ES2015 method definition说它本身不是很优雅,但是有点遗漏了问题的重点,那就是如何避免将名称用引号引起来。我猜想大多数人都在掩盖原来的问题,该问题已经包含了答案,并且实际上正在寻找的东西,正如您所说的,没有得到很好的记录,
craig_h

1
@craig_h超级有趣的一点。我本来只是为了寻找观看嵌套道具的好方法而花了几个小时。这个问题标题是我找到的最接近的问题,但是我想念他在问题正文中列出了引用的道具,但发现答案并不那么优雅。我终于在另一个问题的评论中找到了引用的支持方法,并试图创建一个问题以自我回答,以此作为SO引用的支持方法的文档。但是这个问题正是我的标题,所以我在这里添加了答案。也许我应该提出一个新问题。
罗恩C

9
@RonC这是一个很普遍的问题,您的答案正是很多人正在寻找的东西,所以我认为它很适合您。我们所有人只有有限的时间,而且我们大多数人几乎都没有读过问题,因此,这当然不是对您提供的答案的批评,我只是有点腐,指出我的原始答案是针对问题,并不想被人自以为是。我实际上很喜欢“观看计算”方法,但是,许多人更喜欢复杂程度较低的“报价”方法,您已在回答中简要总结了这些方法。
craig_h

10
官方文档现在包括这个方法
feihcsim

19

VueJs深入观察子对象

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

8

如果要看一会儿财产然后不看它怎么办?

还是看一个库子组件的属性?

您可以使用“动态观察器”:

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

$watch返回一个取消监视功能,如果调用该功能,它将停止监视。

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

您也可以使用以下deep选项:

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

请确保查看文档


2
根据文档,您不应使用箭头功能来定义观察者: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

这里没有看到它,但是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
   }

   ...
}

5

我用来“破解”该解决方案的另一种添加方法是:我设置了一个单独的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...
    }
}

好一个;)但是数组又如何呢?:)
WEBCENTER

3

我对using的可接受答案的问题deep: true是,当深入观察数组时,我无法轻松确定数组的哪个元素包含更改。我找到的唯一清晰的解决方案是这个答案,它说明了如何制作组件,以便您可以分别观察每个数组元素。


1
是的,这是真的,但这不是深度观察者的目的(实际上,您会发现oldVal并且newVal两者都引用相同的对象,因此也是相同的)。a的目的deep watcher是在值更改时执行某些操作,例如发出事件,进行ajax调用等。否则,您可能需要一个组件,如Mani的答案所指出的。
craig_h 18/12/8

我有跟踪单个更改的相同问题!不过,我找到了一个解决方案,并在此处进行了记录
Erik Koopmans

3

跟踪列表中的单个更改项

如果要查看列表中的所有项目并知道列表中的哪个项目已更改,则可以分别在每个项目上设置自定义观察程序,如下所示:

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

如果您的列表不是立即填充的(就像在原始问题中一样),则可以将逻辑移到created所需的位置,例如,在.then()块内。

观看变化中的清单

如果您的列表本身更新为包含新的或已删除的项目,则我已经开发了一种有用的模式,可以“浅”监视列表本身,并在列表更改时动态监视/取消监视项目:

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

0

我的答案都没有奏效。实际上,如果您想监视嵌套数据,并且组件被多次调用。因此,它们被用不同的道具来识别。例如<MyComponent chart="chart1"/> <MyComponent chart="chart2"/> 我的解决方法是创建一个附加的vuex状态变量,我手动对其进行更新以指向最后更新的属性。

这是一个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
        },
}

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

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

现在在任何组件上获取更新:

    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]);
        },
    },
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.