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

Тепер усередині мого дочірнього компонента "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
@Reiner це саме те, що він намагався уникнути у питанні; це просто те саме, що використовується синтаксис ES5. Насправді немає ніяких додаткових витрат на перегляд обчислених, і це корисна техніка в різних ситуаціях.
craig_h

1
Чи можна спостерігати за зміною keysвсередині об'єкта? Приклад:"item.*": function(val){...}
Чарлі

Відповіді:


622

Для цього ви можете використовувати глибокий годинник :

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

Тепер це дозволить виявити будь-які зміни об’єктів у itemмасиві та доповнення до самого масиву (при використанні з Vue.set ). Ось JSFiddle: http://jsfiddle.net/je2rw3rs/

EDIT

Якщо ви не хочете стежити за кожною зміною об'єкта верхнього рівня, а просто хочете менш незручний синтаксис для прямого перегляду вкладених об’єктів, ви можете просто переглянути 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
таким чином, "обробник" буде викликаний, коли будь-яка опора має зміну, моя мета - відокремити обробники, щоб спостерігати, чи відбуваються зміни в "опорі" або в межах myArray в "someOtherProp" окремо
Пластик

9
Якщо ви просто хочете спостерігати за змінами на конкретних вкладених об'єктах, то те, що ви робите, це добре. Якщо ви хочете менш незручний синтаксис, ви можете computedзамість цього переглянути a : jsfiddle.net/c52nda7x
craig_h

Саме те, що я шукав, зараз мені здається такою логікою: створіть обчислюване властивість, яке повертає вкладений опору і стежте за цим. Tnx багато.
Пластик

Хочу лише додати - що другий варіант насправді не сильно відрізняється від оригінального спостерігача. Внутрішньо обчислена властивість є лише спостерігачем за всіма посилаються об'єктами / значеннями функції, яка встановлює значення даних для результату функції. - Тож ви, мабуть, лише зробите код трохи більш перекрученим з тим же ефектом.
Фалько

22
@Falco Я щойно знайшов відповідь у коментарі тут . peerbolte зазначив, що це можна зробити, поставивши назву годинника в одинарні лапки. Я перевірив це, і це працює. Так що в цьому випадку це було б watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} досить гладко. Я люблю Vue!
Рон С

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, оскільки це звичайний випадок використання. Я годинами шукав рішення, дякую за вашу відповідь.
Клавдіу

11
@symba Цікаво, що люди не помітили, що це те саме, що просили уникати ОП, лише в ES5синтаксисі. Ми, звичайно, можемо стверджувати, що ES2015 method definitionсам по собі не дуже елегантний, але він начебто не вистачає суті питання, що полягає в тому, як уникнути обговорення назви цитатами. Я здогадуюсь, що більшість людей переглядають оригінальне запитання, яке вже містить відповідь, і насправді шукають те, що, як ви кажете, не дуже добре задокументовано,
craig_h

1
@craig_h супер цікавий момент. Я спочатку полював годинами, щоб знайти хороший спосіб спостерігати за вкладеною опорою. І цей заголовок питання був найближчим питанням, яке я знайшов, але я пропустив, що він перелічив цитовану опору в тілі питання, але знайшов відповіді не настільки витонченими. Нарешті я знайшов процитований підхід в коментарі до іншого питання і спокусився створити питання просто для того, щоб відповісти на нього як документацію цитованого підходу про підтримку. Але саме це питання було б моїм титулом, тому я додав відповідь тут. Можливо, я мав би створити нове запитання.
Рон С

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Повертає 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
   }

   ...
}

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

Моя проблема з прийнятою відповіддю використання deep: trueполягає в тому, що під час глибокого перегляду масиву я не можу легко визначити, який елемент масиву містить зміни. Єдине чітке рішення, яке я знайшов, - це відповідь, яка пояснює, як зробити компонент, щоб ви могли переглядати кожен елемент масиву окремо.


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]);
        },
    },
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.