Vue.js - Cách xem đúng dữ liệu lồng nhau


413

Tôi đang cố gắng hiểu làm thế nào để theo dõi đúng một số biến thể prop. Tôi có một thành phần cha mẹ (tệp .vue) nhận dữ liệu từ lệnh gọi ajax, đặt dữ liệu vào trong một đối tượng và sử dụng nó để hiển thị một số thành phần con thông qua lệnh v-for, bên dưới đơn giản hóa việc triển khai của tôi:

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

... sau đó bên trong <script>thẻ:

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

các đối tượng mục là như thế này:

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

Bây giờ, bên trong thành phần "người chơi" của con tôi đang cố gắng xem bất kỳ biến thể thuộc tính nào của Vật phẩm và tôi sử dụng:

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

Nó hoạt động nhưng có vẻ hơi khó đối với tôi và tôi đã tự hỏi liệu đây có phải là cách làm đúng đắn hay không. Mục tiêu của tôi là thực hiện một số hành động mỗi khi propthay đổi hoặc myArraycó các yếu tố mới hoặc một số biến thể bên trong những hành động hiện có. Bất kỳ đề nghị sẽ được đánh giá cao.


9
Chỉ cần sử dụng "item.someOtherProp": function (newVal, oldVal){như @Ron đề xuất.
Reiner

1
@Reiner đây chính xác là những gì anh ta đang cố tránh trong câu hỏi; đó chỉ là điều tương tự khi sử dụng cú pháp ES5. Thực tế không có bất kỳ chi phí bổ sung nào trong việc xem máy tính và đó là một kỹ thuật hữu ích trong các tình huống khác nhau.
craig_h

1
Có thể xem sự thay đổi trong tất cả keysbên trong của một đối tượng? Ví dụ:"item.*": function(val){...}
Charlie

Câu trả lời:


622

Bạn có thể sử dụng một người theo dõi sâu cho điều đó:

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

Bây giờ điều này sẽ phát hiện bất kỳ thay đổi nào đối với các đối tượng trong itemmảng và bổ sung vào chính mảng đó (khi được sử dụng với Vue.set ). Đây là một JSFiddle: http://jsfiddle.net/je2rw3rs/

BIÊN TẬP

Nếu bạn không muốn xem mọi thay đổi trên đối tượng cấp cao nhất và chỉ muốn một cú pháp ít khó xử hơn để xem trực tiếp các đối tượng lồng nhau, bạn có thể chỉ cần xem computedthay thế:

var vm = new Vue({
  el: '#app',
  computed: {
    foo() {
      return this.item.foo;
    }
  },
  watch: {
    foo() {
      console.log('Foo Changed!');
    }
  },
  data: {
    item: {
      foo: 'foo'
    }
  }
})

Đây là JSFiddle: http://jsfiddle.net/oa07r5fw/


2
theo cách này, "handler" sẽ được gọi bất cứ khi nào bất kỳ prop nào có biến thể, mục đích của tôi là tách các trình xử lý để quan sát xem các thay đổi có xảy ra với "prop" hay trong myArray trong "someOtherProp" riêng biệt
Nhựa

9
Nếu bạn chỉ muốn xem các thay đổi trên các đối tượng lồng nhau cụ thể thì những gì bạn đang làm là ổn. Nếu bạn muốn một cú pháp ít khó xử hơn, bạn có thể xem computedthay thế: jsfiddle.net/c52nda7x
craig_h

Chính xác những gì tôi đang tìm kiếm, bây giờ nó có vẻ rất logic với tôi: tạo ra một thuộc tính được trả về chỗ dựa lồng nhau và xem nó. Tnx rất nhiều.
Nhựa

Tôi chỉ muốn thêm - rằng tùy chọn thứ hai thực sự không khác nhiều so với người theo dõi ban đầu. Bên trong một thuộc tính được tính toán chỉ là một trình theo dõi trên tất cả các đối tượng / giá trị được tham chiếu trong hàm để đặt giá trị dữ liệu cho kết quả của hàm. - Vì vậy, bạn có thể chỉ làm cho mã phức tạp hơn một chút với hiệu ứng tương tự.
Falco

22
@Falco Tôi mới phát hiện ra câu trả lời trong một bình luận ở đây . ngang hàng chỉ ra rằng nó có thể được thực hiện bằng cách đặt tên đồng hồ trong dấu ngoặc đơn. Tôi đã thử nghiệm điều này và nó hoạt động. Vì vậy, trong trường hợp này, nó sẽ watch: { 'item.foo' : function(newVal, oldVal) { // do work here }} khá đẹp. Tôi yêu Vue!
Ron C

436

Một cách tiếp cận tốt khác và một cách thanh lịch hơn một chút là như sau:

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

(Tôi đã học cách tiếp cận này từ @peerbolte trong bình luận ở đây )


47
cách thanh lịch hơn (không chỉ một chút): P. Điều này nên có trong tài liệu Vue.js vì đây là trường hợp sử dụng phổ biến. Tôi đã tìm kiếm hàng giờ để tìm giải pháp, cảm ơn câu trả lời của bạn.
Claudiu

11
@symba Thật thú vị khi mọi người không nhận thấy rằng đây là điều tương tự mà OP yêu cầu tránh, chỉ bằng ES5cú pháp. Tất nhiên chúng ta có thể lập luận rằng ES2015 method definitionbản thân nó không phải là rất thanh lịch, nhưng nó bỏ lỡ điểm của câu hỏi, đó là làm thế nào để tránh gói tên là dấu ngoặc kép. Tôi đoán rằng hầu hết mọi người đang che đậy câu hỏi ban đầu, trong đó đã có câu trả lời, và thực sự đang tìm kiếm một cái gì đó, như bạn nói, không được ghi chép lại nhiều,
craig_h

1
@craig_h điểm siêu thú vị. Ban đầu tôi đã săn lùng hàng giờ để tìm ra một cách hay để xem một chỗ dựa lồng nhau. Và tiêu đề câu hỏi này là câu hỏi gần nhất mà tôi tìm thấy nhưng tôi đã bỏ lỡ rằng anh ấy đã liệt kê một chỗ dựa được trích dẫn trong phần thân của câu hỏi nhưng thấy câu trả lời không quá tao nhã. Cuối cùng tôi đã tìm thấy cách tiếp cận prop trích dẫn trong bình luận của một câu hỏi khác và đã cố gắng tạo ra một câu hỏi chỉ để tự trả lời nó như là tài liệu của phương pháp prop trích dẫn trên SO. Nhưng câu hỏi này chính xác là tiêu đề của tôi sẽ là gì nên tôi đã thêm câu trả lời vào đây. Có lẽ tôi nên tạo ra một câu hỏi mới.
Ron C

9
@RonC Đây là một câu hỏi phổ biến và câu trả lời của bạn chính xác là những gì nhiều người đang tìm kiếm, vì vậy tôi nghĩ rằng nó nằm ở đây. Tất cả chúng ta chỉ có thời gian hạn chế và hầu hết chúng ta hầu như không đọc câu hỏi, vì vậy đây chắc chắn không phải là một lời chỉ trích về câu trả lời mà bạn cung cấp, tôi chỉ hơi bực bội khi chỉ ra rằng câu trả lời ban đầu của tôi là cụ thể cho câu hỏi và không có ý định để được ý kiến. Tôi thực sự thích cách tiếp cận "xem tính toán", tuy nhiên, nhiều người thích cách tiếp cận "trích dẫn" ít phức tạp hơn, mà bạn đã tóm tắt ngắn gọn trong câu trả lời của mình.
craig_h

10
Các tài liệu chính thức bao gồm cách tiếp cận này ngay bây giờ
feihcsim

19

VueJs theo dõi sâu trong các đối tượng trẻ em

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

8

Làm thế nào nếu bạn muốn xem một tài sản trong một thời gian và sau đó để bỏ xem nó?

Hoặc để xem một tài sản thành phần thư viện con?

Bạn có thể sử dụng "trình theo dõi động":

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

Trả $watchvề một chức năng unwatch sẽ dừng xem nếu nó được gọi.

var unwatch = vm.$watch('a', cb)
// later, teardown the watcher
unwatch()

Ngoài ra, bạn có thể sử dụng deeptùy chọn:

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

Hãy chắc chắn để xem tài liệu


2
Theo các tài liệu , bạn không nên sử dụng các chức năng mũi tên để xác định người theo dõi: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

Không thấy nó được đề cập ở đây, nhưng cũng có thể sử dụng vue-property-decoratormô hình nếu bạn đang mở rộng Vuelớp học của mình .

import { Watch, Vue } from 'vue-property-decorator';

export default class SomeClass extends Vue {
   ...

   @Watch('item.someOtherProp')
   someOtherPropChange(newVal, oldVal) {
      // do something
   }

   ...
}

5

Một cách khác để thêm rằng tôi đã sử dụng để 'hack' giải pháp này là để làm điều này: Tôi thiết lập một computedgiá trị riêng biệt đơn giản sẽ trả về giá trị đối tượng lồng nhau.

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

tốt một cái;) nhưng những gì với mảng? :)
ENTER

3

Vấn đề của tôi với câu trả lời chấp nhận của việc sử dụng deep: true, là khi sâu xem một mảng, tôi không thể dễ dàng xác định phần tử của mảng chứa các thay đổi. Giải pháp rõ ràng duy nhất tôi tìm thấy là câu trả lời này, giải thích cách tạo một thành phần để bạn có thể xem từng thành phần mảng riêng lẻ.


1
Đúng vậy, nhưng đó không phải là những gì một người theo dõi sâu sắc dành cho (thực tế bạn sẽ tìm thấy oldValnewValcả hai đều tham chiếu cùng một đối tượng, vì vậy đều giống nhau). Ý định của a deep watcherlàm một cái gì đó khi các giá trị thay đổi, chẳng hạn như phát ra một sự kiện, thực hiện cuộc gọi ajax, v.v. Nếu không, bạn có thể muốn một thành phần, như được chỉ ra trong câu trả lời của Mani.
craig_h

Tôi đã có cùng một vấn đề theo dõi các thay đổi cá nhân! Tôi đã tìm thấy một giải pháp mặc dù, và tài liệu ở đây .
Erik Koopmans

3

Theo dõi các mục thay đổi cá nhân trong một danh sách

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

Nếu danh sách của bạn không được điền ngay lập tức (như trong câu hỏi ban đầu), bạn có thể di chuyển logic ra khỏi createdbất cứ nơi nào cần thiết, ví dụ như bên trong .then()khối.

Xem danh sách thay đổi

Nếu danh sách của bạn tự cập nhật để có các mục mới hoặc đã xóa, tôi đã phát triển một mẫu hữu ích "nông" xem chính danh sách đó và đồng hồ động / bỏ theo dõi các mục khi danh sách thay đổi:

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

Không có câu trả lời cho tôi đã làm việc. Trên thực tế nếu bạn muốn xem dữ liệu lồng nhau với các Thành phần được gọi nhiều lần. Vì vậy, họ được gọi với các đạo cụ khác nhau để xác định chúng. Ví dụ<MyComponent chart="chart1"/> <MyComponent chart="chart2"/> Cách giải quyết của tôi là tạo một biến trạng thái vuex bổ sung, mà tôi cập nhật thủ công để trỏ đến thuộc tính được cập nhật lần cuối.

Dưới đây là ví dụ triển khai 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
        },
}

Trên bất kỳ chức năng Thành phần nào để cập nhật cửa hàng:

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

Bây giờ trên bất kỳ Thành phần nào để có được bản cập nhật:

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