Vue-객체 배열을 깊이 관찰하고 변화를 계산합니까?


108

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

Frank가 방금 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.)

그리고 내가 만든 코드 펜 에서 결과는 내가 예상했던대로 변경된 변경된 객체가 아니라 빈 배열입니다.

왜 이런 일이 발생했는지 또는 내가 여기서 잘못되었는지 제안 할 수 있다면 크게 감사하겠습니다. 많은 감사합니다!

답변:


136

이전 값과 새 값 간의 비교 기능에 문제가 있습니다. 나중에 디버깅 노력을 증가 시키므로 일을 너무 복잡하게하지 않는 것이 좋습니다. 간단하게 유지해야합니다.

가장 좋은 방법은 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>


그것은 실제로 작동하는 솔루션이지만 전적으로 내 사용 사례에 따른 것은 아닙니다. 실제로 앱과 하나의 구성 요소가 있으며 구성 요소는 vue-material 테이블을 사용하고 값을 인라인으로 편집 할 수있는 데이터를 나열합니다. 값 중 하나를 변경 한 다음 변경된 내용을 확인하려고합니다.이 경우에는 실제로 전후 배열을 비교하여 어떤 차이가 있는지 확인합니다. 문제를 해결하기 위해 솔루션을 구현할 수 있습니까? 실제로 나는 아마 그렇게 할 수도 있지만 그냥 VUE 물질 내에서이 점에서 유효한 무슨의 흐름에 대해 일하게 될 것이라고 느낀다
크레이그 밴 퇴 네르

2
참고로 시간을내어 설명 해주셔서 감사합니다. Vue에 대해 더 많이 알 수있게되어 감사합니다!
크레이그 밴 퇴 네르

그것은이 이해 걸 렸어요하지만 :) 피하기 혼란과 더 문제하려는 경우 절대적으로 오른쪽이 마치 마법처럼 작품과는 일을 할 수있는 올바른 방법이다있어
크레이그 밴 퇴 네르

1
나는 이것도 눈치 채고 같은 생각을 가지고 있었지만 객체에도 포함 된 것은 값을 포함하는 값 인덱스입니다. 게터와 세터가 있지만 비교하면 더 나은 이해가 부족하기 때문에 무시합니다. 어떤 프로토 타입에서도 평가하지 않습니다. 다른 답변 중 하나는 그것이 작동하지 않는 이유를 제공합니다 .newVal과 oldVal이 같은 것이기 때문입니다. 조금 복잡하지만 몇 군데에서 해결되었지만 다른 답변은 쉽게 만들 수 있도록 적절한 해결 방법을 제공합니다. 비교 목적을위한 불변 객체.
크레이그 밴 퇴 네르

1
하지만 궁극적으로 여러분의 방식은 한 눈에 이해하기 쉽고 가치가 변경 될 때 사용할 수있는 것에 대해 더 많은 유연성을 제공합니다. Vue에서 단순하게 유지하는 것의 이점을 이해하는 데 많은 도움이되었지만 다른 질문에서 보셨 듯이 약간 고착되었습니다. 감사합니다! :)
크레이그 밴 퇴 네르

21

문제를 해결하기 위해 구현을 변경했으며 이전 변경 사항을 추적하고이를 비교하는 개체를 만들었습니다. 문제를 해결하는 데 사용할 수 있습니다.

여기에서 이전 값이 별도의 변수에 저장되고 시계에서 사용되는 메서드를 만들었습니다.

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

업데이트 된 코드 펜보기


따라서 마운트되면 데이터 사본을 저장하고이를 사용하여 비교합니다. 흥미롭지 만 내 사용 사례는 더 복잡 할 것이며 배열에서 객체를 추가하고 제거 할 때 어떻게 작동하는지 확신 할 수 없습니다. @Quirk도 문제를 해결하기위한 좋은 링크를 제공했습니다. 하지만 사용할 수 있는지 몰랐습니다 vm.$data. 감사합니다!
크레이그 밴 퇴 네르

예, 시계 후에 메서드를 다시 호출하여 업데이트하고 있습니다. 원래 값으로 돌아 가면 변경 사항도 추적됩니다.
Viplock

Ohhh, 나는 거기에 숨어있는 것이 훨씬 의미가 있으며 이것을 처리하는 덜 복잡한 방법이라는 것을 알지 못했습니다 (github의 솔루션과 반대).
크레이그 밴 퇴 네르

그리고 나중에 원래 배열에서 무언가를 추가하거나 제거하는 경우 메서드를 다시 호출하면 솔루션을 다시 사용하는 것이 좋습니다.
Viplock

1
_.cloneDeep () 제 경우에는 정말 도움이되었습니다. 감사합니다!! 정말 도움이되었습니다!
Cristiana Pereira 2018

18

잘 정의 된 행동입니다. 변경된 개체에 대한 이전 값을 가져올 수 없습니다 . newVal와 둘 다 oldVal동일한 객체를 참조하기 때문 입니다. Vue는 변경 한 개체의 이전 복사본을 유지 하지 않습니다 .

개체를 다른 개체 로 교체 했다면 Vue가 올바른 참조를 제공했을 것입니다.

문서Note섹션을 읽으십시오 . ( vm.$watch)

여기여기 에 대한 자세한 내용 .


3
오 마이 모자, 정말 고마워! 그것은 까다로운 것입니다 ... 나는 val과 oldVal이 다를 것이라고 완전히 예상했지만 그것들을 조사한 후에 나는 그것이 새로운 배열의 두 복사본임을 알았습니다. 이전에는 그것을 추적하지 않습니다. 더 많은 비트를 읽고 같은 오해에 대한이 답 SO 질문을 발견 : stackoverflow.com/questions/35991494/...
크레이그 밴 퇴 네르에게

5

이것은 내가 물체를 깊이 관찰하는 데 사용하는 것입니다. 내 요구 사항은 개체의 자식 필드를 관찰하는 것이 었습니다.

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

stackoverflow.com/a/41136186/2110294 에 설명 된 경고에 대한 이해를 놓치고있을 수 있다고 생각합니다 . 분명히 말하면 이것은 질문에 대한 해결책이 아니며 특정 상황에서 예상했던대로 작동하지 않습니다.
Craig van Tonder

이것이 바로 내가 찾고 있던 것입니다!. 감사합니다
Jaydeep Shil

여기도 똑같아, 정확히 내가 필요한 것 !! 감사.
Guntar

4

구성 요소 솔루션과 딥 클론 솔루션에는 장점이 있지만 다음과 같은 문제도 있습니다.

  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()변경된 특정 목록 항목을 받게됩니다. 여기서 원하는대로 처리 할 수 ​​있습니다.

또한 목록에 항목을 추가 / 제거하는 경우 (이미있는 항목 만 조작하는 것이 아니라) 더 복잡한 시나리오를 여기에 문서화했습니다 .


Erik에게 감사합니다. 유효한 포인트를 제시하고 제공된 방법론은 질문에 대한 솔루션으로 구현 된 경우 가장 유용합니다.
Craig van Tonder
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.