Vue - Xem sâu một mảng đối tượng và tính toán sự thay đổi?


108

Tôi có một mảng được gọi là peoplechứa các đối tượng như sau:

Trước

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

Nó có thể thay đổi:

Sau

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

Lưu ý Frank vừa tròn 33 tuổi.

Tôi có một ứng dụng mà tôi đang cố gắng xem mảng người và khi bất kỳ giá trị nào thay đổi, hãy ghi lại thay đổi:

<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>

Tôi dựa trên câu hỏi mà tôi đã hỏi ngày hôm qua về so sánh mảng và chọn câu trả lời làm việc nhanh nhất.

Vì vậy, tại thời điểm này, tôi mong đợi sẽ thấy kết quả của: { id: 1, name: 'Frank', age: 33 }

Nhưng tất cả những gì tôi nhận lại được trong bảng điều khiển là (nhớ rằng tôi đã có nó trong một thành phần):

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

Và trong codepen mà tôi đã thực hiện , kết quả là một mảng trống và không phải đối tượng đã thay đổi đã thay đổi, điều mà tôi mong đợi.

Nếu ai đó có thể gợi ý lý do tại sao điều này lại xảy ra hoặc tôi đã sai ở đâu ở đây thì nó sẽ được đánh giá rất cao, cảm ơn rất nhiều!

Câu trả lời:


136

Chức năng so sánh giữa giá trị cũ và giá trị mới của bạn đang gặp một số vấn đề. Tốt hơn là không nên phức tạp hóa mọi thứ quá nhiều, vì nó sẽ làm tăng nỗ lực gỡ lỗi của bạn sau này. Bạn nên giữ nó đơn giản.

Cách tốt nhất là tạo person-componentvà theo dõi từng người riêng biệt bên trong thành phần của chính nó, như được hiển thị bên dưới:

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

Vui lòng xem bên dưới một ví dụ hoạt động để xem thành phần người bên trong. Nếu bạn muốn xử lý nó ở phía phụ huynh, bạn có thể sử dụng $emitđể gửi một sự kiện lên trên, có chứa người idđã sửa đổi.

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>


Đó thực sự là một giải pháp hiệu quả nhưng nó không hoàn toàn theo trường hợp sử dụng của tôi. Bạn thấy đấy, trên thực tế, tôi có ứng dụng và một thành phần, thành phần sử dụng bảng vue-material và liệt kê dữ liệu với khả năng chỉnh sửa các giá trị trong dòng. Tôi đang cố gắng thay đổi một trong các giá trị sau đó kiểm tra những gì đã thay đổi, vì vậy trong trường hợp này, nó thực sự so sánh mảng trước và sau để xem có sự khác biệt nào. Tôi có thể thực hiện giải pháp của bạn để giải quyết vấn đề không? Trên thực tế tôi có khả năng có thể làm như vậy nhưng chỉ cảm thấy rằng nó sẽ được làm việc chống lại dòng chảy của những gì có sẵn trong lĩnh vực này trong vòng vue vật chất
Craig van Tonder

2
Nhân tiện, cảm ơn bạn đã dành thời gian giải thích điều này, nó đã giúp tôi tìm hiểu thêm về Vue mà tôi đánh giá cao!
Craig van Tonder

Tôi đã mất một thời gian để hiểu được điều này, nhưng bạn đang hoàn toàn đúng, các công trình này như một nét duyên dáng và là cách chính xác để làm những việc nếu bạn muốn nhầm lẫn tránh và các vấn đề hơn nữa :)
Craig van Tonder

1
Tôi cũng nhận thấy điều này và có cùng suy nghĩ nhưng những gì cũng được chứa trong đối tượng là chỉ mục giá trị chứa giá trị, các getters và setters ở đó nhưng khi so sánh thì nó bỏ qua chúng, vì thiếu hiểu biết hơn, tôi nghĩ nó có không đánh giá trên bất kỳ nguyên mẫu nào. Một trong những câu trả lời khác cung cấp lý do tại sao nó không hoạt động, đó là bởi vì newVal và oldVal là một thứ giống nhau, nó hơi phức tạp nhưng là một cái gì đó đã được giải quyết ở một số nơi, nhưng một câu trả lời khác cung cấp một công việc tốt để dễ dàng tạo một đối tượng bất biến cho mục đích so sánh.
Craig van Tonder

1
Tuy nhiên, cuối cùng, cách của bạn dễ hiểu hơn trong nháy mắt và cung cấp tính linh hoạt hơn về những gì có sẵn khi giá trị thay đổi. Nó đã giúp tôi rất nhiều để hiểu được lợi ích của việc giữ cho nó đơn giản trong Vue nhưng hơi bị mắc kẹt với nó như bạn đã thấy trong câu hỏi khác của tôi. Cảm ơn nhiều! :)
Craig van Tonder

21

Tôi đã thay đổi việc triển khai nó để giải quyết vấn đề của bạn, tôi đã tạo một đối tượng để theo dõi các thay đổi cũ và so sánh với nó. Bạn có thể sử dụng nó để giải quyết vấn đề của mình.

Ở đây tôi đã tạo một phương thức, trong đó giá trị cũ sẽ được lưu trữ trong một biến riêng biệt và sau đó sẽ được sử dụng trong đồng hồ.

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

Xem codepen cập nhật


Vì vậy, khi nó được gắn kết, hãy lưu trữ một bản sao của dữ liệu và sử dụng nó để so sánh với nó. Thật thú vị, nhưng trường hợp sử dụng của tôi sẽ phức tạp hơn và tôi không chắc điều đó sẽ hoạt động như thế nào khi thêm và xóa các đối tượng khỏi mảng, @Quirk cũng cung cấp các liên kết tốt để giải quyết vấn đề. Nhưng tôi không biết rằng bạn có thể sử dụng vm.$data, cảm ơn bạn!
Craig van Tonder

có, và tôi đang cập nhật nó sau đồng hồ cũng bằng cách gọi lại phương thức, bởi nó nếu bạn quay trở lại giá trị ban đầu, thì nó cũng sẽ theo dõi sự thay đổi.
Viplock

Ồ, tôi không nhận thấy rằng ẩn ở đó, có nhiều ý nghĩa và là một cách ít phức tạp hơn để giải quyết vấn đề này (trái ngược với giải pháp trên github).
Craig van Tonder

và ya, nếu bạn đang thêm hoặc xóa thứ gì đó khỏi mảng ban đầu, chỉ cần gọi lại phương thức và bạn có thể thực hiện lại giải pháp.
Viplock

1
_.cloneDeep () thực sự hữu ích trong trường hợp của tôi. Cảm ơn bạn!! Thực sự hữu ích!
Cristiana Pereira

18

Đó là hành vi được xác định rõ. Bạn không thể lấy giá trị cũ cho một đối tượng bị đột biến . Đó là bởi vì cả newValoldValđều tham chiếu đến cùng một đối tượng. Vue sẽ không giữ một bản sao cũ của một đối tượng mà bạn đã đột biến.

Nếu bạn thay thế đối tượng bằng một đối tượng khác, Vue sẽ cung cấp cho bạn các tham chiếu chính xác.

Đọc Notephần trong tài liệu. ( vm.$watch)

Thêm về điều này ở đâyở đây .


3
Oh my hat, Cảm ơn rất nhiều! Đó là một điều khó khăn ... Tôi hoàn toàn mong đợi val và oldVal sẽ khác nhau nhưng sau khi kiểm tra chúng, tôi thấy đó là hai bản sao của mảng mới, nó không theo dõi nó trước đây. Đọc thêm một chút và tìm thấy câu hỏi SO chưa được trả lời này liên quan đến sự hiểu lầm tương tự: stackoverflow.com/questions/35991494/…
Craig van Tonder

5

Đây là những gì tôi sử dụng để quan sát sâu một đối tượng. Yêu cầu của tôi là xem các trường con của đối tượng.

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

Tôi tin rằng bạn có thể thiếu hiểu biết về cavet được mô tả trong stackoverflow.com/a/41136186/2110294 . Chỉ cần nói rõ, đây không phải là một giải pháp cho câu hỏi và sẽ không hoạt động như bạn có thể mong đợi trong một số tình huống nhất định.
Craig van Tonder

đây chính xác là những gì tôi đang tìm !. Cảm ơn
Jaydeep Shil,

Ở đây cũng vậy, chính xác những gì tôi cần !! Cảm ơn.
Guntar

4

Giải pháp thành phần và giải pháp nhân bản sâu có những ưu điểm của chúng, nhưng cũng có những vấn đề:

  1. Đôi khi bạn muốn theo dõi các thay đổi trong dữ liệu trừu tượng - không phải lúc nào việc xây dựng các thành phần xung quanh dữ liệu đó cũng có ý nghĩa.

  2. Việc sao chép sâu toàn bộ cấu trúc dữ liệu của bạn mỗi khi bạn thực hiện thay đổi có thể rất tốn kém.

Tôi nghĩ có một cách tốt hơn. Nếu bạn muốn xem tất cả các mục trong một danh sách và bí quyết mục trong danh sách thay đổi, bạn có thể thiết lập theo dõi tùy chỉnh trên tất cả các mục riêng biệt, như vậy:

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

Với cấu trúc này, handleChange()sẽ nhận được mục danh sách cụ thể đã thay đổi - từ đó bạn có thể thực hiện bất kỳ thao tác nào bạn muốn.

Tôi cũng đã ghi lại một tình huống phức tạp hơn ở đây , trong trường hợp bạn đang thêm / xóa các mục vào danh sách của mình (thay vì chỉ thao tác với các mục đã có).


Cảm ơn Erik, bạn trình bày những điểm hợp lệ và phương pháp được cung cấp chắc chắn hữu ích nhất nếu được thực hiện như một giải pháp cho câu hỏi.
Craig van Tonder
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.