Vue.js - 如何正确观察嵌套数据

Pla*_*tic 323 javascript vue.js vue-resource vue-component

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

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

...然后在<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]);
            }
    });
}
Run Code Online (Sandbox Code Playgroud)

item对象是这样的:

item:{
   prop: value,
   someOtherProp: {
       nestedProp: nestedValue,
       myArray: [{type: "a", num: 1},{type: "b" num: 6} ...]
    },
}
Run Code Online (Sandbox Code Playgroud)

现在,在我的孩子"玩家"组件中,我正在尝试观察任何Item的属性变化,我使用:

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

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

cra*_*g_h 493

您可以使用深度观察者:

watch: {
  item: {
     handler(val){
       // do stuff
     },
     deep: true
  }
}
Run Code Online (Sandbox Code Playgroud)

现在,它将检测对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'
    }
  }
})
Run Code Online (Sandbox Code Playgroud)

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

  • @Falco我刚刚在[评论](/sf/ask/2108717691/#comment78218231_45588104)中找到答案.peerbolte表示可以通过将手表名称放在单引号中来完成.我测试了这个并且它有效.所以在这种情况下,它应该是`watch:{'item.foo':function(newVal,oldVal){//在这里工作}}`非常漂亮.我爱Vue! (9认同)
  • 对于深度观察者,观察者接受 args `newValue`、`oldValue`,但对于对象,我发现它传递相同的值两个 args。(可能是为了性能——他们不想对结构进行深层复制。)这意味着你无法判断 * 为什么 * 然后根据参数调用处理程序。(如果这对你来说是个问题。) (9认同)
  • 如果您只想查看特定嵌套对象的更改,那么您正在做的事情很好.如果你想要一个不那么笨拙的语法,你可以看一下`computed`而不是:http://jsfiddle.net/c52nda7x/ (7认同)
  • 以这种方式,只要任何道具有变异,就会调用"处理程序",我的目的是分离处理程序,以便观察变化是发生在"prop"还是在myArray中的"someOtherProp"中 (2认同)

Ron*_*n C 358

另一种更好的方法和更优雅的方法如下:

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

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

  • 更优雅的方式(不仅仅是一点点):P.这应该在Vue.js文档中,因为它是一个常见的用例.我一直在寻找解决方案的时间,感谢您的回答. (39认同)
  • [官方文档](https://vuejs.org/v2/api/#watch)现在包含这种方法 (9认同)
  • @symba有趣的是,人们没有注意到这与OP要求避免的一样,只是在"ES5"语法中.我们当然可以争辩说[ES2015方法定义](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Method_definitions)本身并不是很优雅,但它很亲切错过了问题的重点,这是如何避免包装名称的引号.我猜大多数人都在掩饰原来的问题,这个问题已经包含了答案,并且实际上正在寻找一些东西,正如你所说的,没有很好的记录, (8认同)
  • @RonC这是一个很受欢迎的问题,你的答案正是很多人都在寻找的,所以我认为它在这里很好.我们所有人都只有有限的时间,我们大多数人几乎都没有读过这些问题,所以这肯定不是对你提供的答案的批评,我只是有点迂腐指出我的原始答案是特定于问题,并不打算自以为是.我实际上喜欢"观看计算"的方法,然而,许多人更喜欢那种不太复杂的"引号"方法,你在答案中简明扼要地总结了这种方法. (8认同)
  • 对于这个解决方案+1,只需记住您也可以使用以下语法:`'item.someOtherProp'(newVal, oldVal){ .. }` (7认同)
  • 第一次见火,为什么?@feihcsim (3认同)

Alp*_*glu 9

VueJs深入观察子对象

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


get*_*lad 9

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

   ...
}
Run Code Online (Sandbox Code Playgroud)


Zac*_*Zac 8

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

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


Jon*_*ias 8

我发现它也可以这样工作:

watch: {
    "details.position"(newValue, oldValue) {
        console.log("changes here")
    }
},
data() {
    return {
      details: {
          position: ""
      }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 8

我使用了 deep:true,但发现监视函数中的旧值和新值始终相同。作为以前解决方案的替代方案,我尝试了这个,它将通过将整个对象转换为字符串来检查整个对象的任何更改:

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


Eri*_*ans 7

跟踪列表中的单个更改项

如果您想查看列表中的所有项目并知道列表中的哪个项目发生了变化,您可以分别为每个项目设置自定义观察器,如下所示:

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});
    });
  },
});
Run Code Online (Sandbox Code Playgroud)

如果您的列表没有立即填充(就像在原始问题中一样),您可以将逻辑移出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();
  },
});
Run Code Online (Sandbox Code Playgroud)


小智 7

对我来说,没有一个答案有效。实际上,如果您想观看多次调用组件的嵌套数据。因此,它们被称为不同的 props 来识别它们。例如,<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
        },
}
Run Code Online (Sandbox Code Playgroud)

在任何更新存储的组件函数上:

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

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

    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]);
        },
    },
Run Code Online (Sandbox Code Playgroud)


rol*_*oli 6

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

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

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

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

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

var unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()
Run Code Online (Sandbox Code Playgroud)

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

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

请确保查看文档

  • 根据[文档](https://vuejs.org/v2/api/#watch),您不应使用箭头函数来定义观察者:`请注意,您不应使用箭头函数来定义观察者(例如searchQuery:newValue =&gt; this.updateAutocomplete(newValue))。原因是箭头函数绑定了父上下文,因此 this 不会是您期望的 Vue 实例,并且 this.updateAutocomplete 将是未定义的。 (7认同)

Rah*_*zir 6

对于任何正在寻找 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
);

Run Code Online (Sandbox Code Playgroud)

您可以参考此处的文档:https ://v3.vuejs.org/guide/reactivity-compulated-watchers.html#watch


kru*_*ubo 5

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

  • 是的,这是真的,但这不是深度观察者的用途(事实上,你会发现 `oldVal` 和 `newVal` 都引用同一个对象,所以是相同的)。`deep watcher` 的目的是在值改变时*做某事*,例如发出事件、进行 ajax 调用等。否则,您可能需要一个组件,正如 Mani 的回答中所指出的那样。 (3认同)

Las*_*M4N 5

我个人更喜欢这种干净的实现:

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

  }
}
Run Code Online (Sandbox Code Playgroud)

  • 谢谢。这非常有效。尽管我发现您可以通过侦听字符串作为“myVariable”名称来侦听嵌套值。`'myObject.property': { handler(new, old) { // 在这里做一些事情 } }` (4认同)