Vue - Глубокий просмотр массива объектов и вычисление изменений?

У меня есть массив под названием people который содержит объекты следующим образом:

До

[
  {id: 0, name: 'Bob', age: 27},
  {id: 1, name: 'Frank', age: 32},
  {id: 2, name: 'Joe', age: 38}
]

Это может измениться:

После

[
  {id: 0, name: 'Bob', age: 27},
  {id: 1, name: 'Frank', age: 33},
  {id: 2, name: 'Joe', age: 38}
]

Обратите внимание, Фрэнк только что исполнилось 33 года.

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

<style>
input {
  display: block;
}
</style>

<div id="app">
  <input type="text" v-for="(person, index) in people" v-model="people[index].age" />
</div>

<script>
new Vue({
  el: '#app',
  data: {
    people: [
      {id: 0, name: 'Bob', age: 27},
      {id: 1, name: 'Frank', age: 32},
      {id: 2, name: 'Joe', age: 38}
    ]
  },
  watch: {
    people: {
      handler: function (val, oldVal) {
        // Return the object that changed
        var changed = val.filter( function( p, idx ) {
          return Object.keys(p).some( function( prop ) {
            return p[prop] !== oldVal[idx][prop];
          })
        })
        // Log it
        console.log(changed)
      },
      deep: true
    }
  }
})
</script>

Я основал это на вопросе, который я задал вчера о сравнениях массивов, и выбрал самый быстрый рабочий ответ.

Итак, на данный момент я ожидаю увидеть результат: { id: 1, name: 'Frank', age: 33 }

Но все, что я возвращаю в консоли, (имея в виду, что это было в компоненте):

[Vue warn]: Error in watcher "people" 
(found in anonymous component - use the "name" option for better debugging messages.)

И в коде, который я сделал, результатом является пустой массив, а не измененный объект, который изменился, что было бы тем, что я ожидал.

Если бы кто-нибудь мог подсказать, почему это происходит или где я ошибся здесь, это было бы очень признательно, большое спасибо!

7 ответов

Решение

Ваша функция сравнения между старым значением и новым значением имеет некоторые проблемы. Лучше не слишком усложнять ситуацию, так как это увеличит ваши усилия по отладке позже. Вы должны держать это простым.

Лучший способ - это создать person-component и наблюдайте за каждым человеком отдельно внутри его собственного компонента, как показано ниже:

<person-component :person="person" v-for="person in people"></person-component>

Ниже приведен рабочий пример для просмотра внутреннего компонента. Если вы хотите справиться с этим на родительской стороне, вы можете использовать $emit отправить событие вверх, содержащее id модифицированного человека.

Vue.component('person-component', {
    props: ["person"],
    template: `
        <div class="person">
            {{person.name}}
            <input type='text' v-model='person.age'/>
        </div>`,
    watch: {
        person: {
            handler: function(newValue) {
                console.log("Person with ID:" + newValue.id + " modified")
                console.log("New age: " + newValue.age)
            },
            deep: true
        }
    }
});

new Vue({
    el: '#app',
    data: {
        people: [
          {id: 0, name: 'Bob', age: 27},
          {id: 1, name: 'Frank', age: 32},
          {id: 2, name: 'Joe', age: 38}
        ]
    }
});
<script src="https://unpkg.com/vue@2.1.5/dist/vue.js"></script>
<body>
    <div id="app">
        <p>List of people:</p>
        <person-component :person="person" v-for="person in people"></person-component>
    </div>
</body>

Я изменил его реализацию, чтобы решить вашу проблему, я создал объект для отслеживания старых изменений и сравнения с ним. Вы можете использовать его для решения вашей проблемы.

Здесь я создал метод, в котором старое значение будет храниться в отдельной переменной и который затем будет использоваться в часах.

new Vue({
  methods: {
    setValue: function() {
      this.$data.oldPeople = _.cloneDeep(this.$data.people);
    },
  },
  mounted() {
    this.setValue();
  },
  el: '#app',
  data: {
    people: [
      {id: 0, name: 'Bob', age: 27},
      {id: 1, name: 'Frank', age: 32},
      {id: 2, name: 'Joe', age: 38}
    ],
    oldPeople: []
  },
  watch: {
    people: {
      handler: function (after, before) {
        // Return the object that changed
        var vm = this;
        let changed = after.filter( function( p, idx ) {
          return Object.keys(p).some( function( prop ) {
            return p[prop] !== vm.$data.oldPeople[idx][prop];
          })
        })
        // Log it
        vm.setValue();
        console.log(changed)
      },
      deep: true,
    }
  }
})

Смотрите обновленный кодекс

Это хорошо определенное поведение. Вы не можете получить старое значение для мутированного объекта. Это потому, что оба newVal а также oldVal ссылаются на тот же объект. Vue не будет хранить старую копию объекта, который вы мутировали.

Если бы вы заменили объект другим, Вью предоставил бы вам правильные ссылки.

Прочитайте Note раздел в документах. ( vm.$watch )

Подробнее об этом здесь и здесь.

Компонентное решение и решение с глубоким клонированием имеют свои преимущества, но также имеют проблемы:

  1. Иногда вы хотите отслеживать изменения в абстрактных данных - не всегда имеет смысл создавать компоненты на основе этих данных.

  2. Глубокое клонирование всей структуры данных каждый раз, когда вы вносите изменения, может быть очень дорогостоящим.

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

var vm = new Vue({
  data: {
    list: [
      {name: 'obj1 to watch'},
      {name: 'obj2 to watch'},
    ],
  },
  methods: {
    handleChange (newVal) {
      // Handle changes here!
      console.log(newVal);
    },
  },
  created () {
    this.list.forEach((val) => {
      this.$watch(() => val, this.handleChange, {deep: true});
    });
  },
});

С этой структурой, handleChange() получит конкретный элемент списка, который был изменен - ​​оттуда вы можете выполнять любые действия, которые вам нравятся.

Я также задокументировал здесь более сложный сценарий на случай, если вы добавляете / удаляете элементы в свой список (а не только манипулируете элементами, которые уже есть).

Это то, что я использую для глубокого наблюдения за объектом. Моим требованием было наблюдение за дочерними полями объекта.

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

если у нас есть объект или массив объектов, и мы хотим смотреть их в Vuejs или NUXTJS, нужно использовать deep: true в часы

      watch: {
  'Object.key': {
     handler (val) {
         console.log(val)
     },
     deep: true
   } 
 }

 watch: {
  array: {
     handler (val) {
         console.log(val)
     },
     deep: true
   } 
 }

Вместо "часы" я решил проблему с "вычислением"!

Я не тестировал этот код, но думаю, что он должен работать. Пожалуйста, сообщите мне в комментариях, если нет.

      <script>
new Vue({
  el: '#app',
  data: {
    people: [
      {id: 0, name: 'Bob', age: 27},
      {id: 1, name: 'Frank', age: 32},
      {id: 2, name: 'Joe', age: 38}
    ],
    oldVal: {},
    peopleComputed: computed({
      get(){
        this.$data.oldVal = { ...people };
        return people;
      },
      set(val){
        // Return the object that changed
        var changed = val.filter( function( p, idx ) {
          return Object.keys(p).some( function( prop ) {
            return p[prop] !== this.$data.oldVal[idx][prop];
          })
        })
        // Log it
        console.log(changed)
        this.$data.people = val;
      }
    }),
  }
})
</script>
Другие вопросы по тегам