Vue.js - Как правильно следить за вложенными данными

414

Я пытаюсь понять, как правильно наблюдать за некоторым изменением опоры. У меня есть родительский компонент (файлы .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 выглядят так:

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

Теперь внутри моего дочернего компонента "player" я пытаюсь следить за изменением свойств любого элемента и использую:

...
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.
Райнер
1
@ Рейнер это именно то, что он пытался избежать в этом вопросе; это то же самое, что с использованием синтаксиса ES5. На самом деле нет никаких дополнительных затрат при просмотре вычислений, и это полезная техника в различных ситуациях.
craig_h
1
Можно ли наблюдать за изменениями во всем keysвнутри объекта? Пример:"item.*": function(val){...}
Чарли

Ответы:

624

Вы можете использовать глубокий наблюдатель для этого:

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/

craig_h
источник
2
таким образом, «обработчик» будет вызываться всякий раз, когда у любого пропа есть вариация, моя цель состоит в том, чтобы отделить обработчики, чтобы наблюдать, происходит ли изменение как «проп» или внутри myArray в «someOtherProp» отдельно
Plastic
9
Если вы просто хотите следить за изменениями в определенных вложенных объектах, то то, что вы делаете, это хорошо. Если вам нужен менее неловкий синтаксис, вы можете посмотреть computedвместо этого: jsfiddle.net/c52nda7x
craig_h
Именно то, что я искал, теперь мне кажется такой логичной: создать вычисляемое свойство, которое возвращает вложенную подпорку, и следить за ним. TNX много.
Пластик
Я просто хочу добавить - что второй вариант действительно мало чем отличается от оригинального наблюдателя. Внутренне вычисляемое свойство является просто наблюдателем для всех ссылочных объектов / значений в функции, который устанавливает значение данных для результата функции. - Таким образом, вы, вероятно, сделаете код немного более запутанным с тем же эффектом.
Falco
22
@Falco Я только что нашел ответ в комментарии здесь . peerbolte указал, что это можно сделать, поместив название часов в одинарные кавычки. Я проверил это, и это работает. Так что в этом случае это было бы watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} довольно гладко. Я люблю Vue!
Рон С
437

Еще один хороший и немного более элегантный подход заключается в следующем:

 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, так как это обычный случай использования. Я часами искал решение, спасибо за ваш ответ.
Клаудиу
11
@symba Интересно, что люди не заметили, что это то же самое, что ОП просил избегать, только в ES5синтаксисе. Можно, конечно, утверждать, что ES2015 method definitionсамо по себе это не очень элегантно, но оно как бы упускает из виду вопрос о том, как избежать переноса названия в кавычки. Я предполагаю, что большинство людей затушевывают оригинальный вопрос, который уже содержит ответ, и на самом деле ищут что-то, что, как вы говорите, не очень хорошо задокументировано,
craig_h
1
@craig_h супер интересный момент. Первоначально я часами охотился, чтобы найти хороший способ посмотреть вложенную опору. И этот вопрос был самым близким вопросом, который я нашел, но я упустил, что он перечислил цитату в тексте вопроса, но нашел ответы не столь элегантными. Я, наконец, нашел цитируемый подход prop в комментарии к другому вопросу, и у меня возник соблазн создать вопрос, чтобы самостоятельно ответить на него в качестве документации к цитируемому подходу prop для SO. Но этот вопрос был именно таким, каким был бы мой заголовок, поэтому я добавил ответ здесь. Может быть, я должен был создать новый вопрос.
Рон С
9
@RonC Это популярный вопрос, и ваш ответ - именно то, что ищут многие люди, так что я думаю, что это хорошо. У всех нас есть только ограниченное время, и большинство из нас едва читают вопросы, так что это, конечно, не критика ответа, который вы дали, я просто немного педантичен, отмечая, что мой первоначальный ответ был конкретным для вопрос и не предназначен для самоуверенности. Мне действительно нравится подход «просмотр вычислений», однако многие предпочитают менее замысловатый подход «цитаты», который вы кратко суммировали в своем ответе.
craig_h
10
Официальная документация включает в себя этот подход в настоящее время
feihcsim
20

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Возвращает unwatch функцию , которая будет остановить смотреть , если она называется.

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
   }

   ...
}
getglad
источник
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...
    }
}
Zac
источник
хороший;) а что с массивами? :)
WEBCENTER
3

Моя проблема с принятым ответом на использование deep: trueзаключается в том, что при глубоком просмотре массива я не могу легко определить, какой элемент массива содержит изменение. Единственное ясное решение, которое я нашел, - это ответ, который объясняет, как создать компонент, чтобы вы могли наблюдать за каждым элементом массива индивидуально.

krubo
источник
1
Да, это правда, но это не то, для чего нужен глубокий наблюдатель (на самом деле вы найдете oldValи newValоба ссылаетесь на один и тот же объект, поэтому они одинаковы). Намерение a deep watcherсостоит в том, чтобы что-то сделать, когда значения изменяются, например, отправить событие, сделать ajax-вызов и т. Д. В противном случае вам, вероятно, понадобится компонент, как указано в ответе Мани.
craig_h
У меня была такая же проблема отслеживания отдельных изменений! Хотя я нашел решение и задокументировал его здесь .
Эрик Купманс
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
        },
}

На любой компонент функции для обновления магазина:

    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]);
        },
    },
thomasL
источник