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

สังเกตว่าแฟรงค์เพิ่งอายุ 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.)

และในcodepen ที่ฉันสร้างผลลัพธ์ก็คืออาร์เรย์ว่างเปล่าและไม่ใช่วัตถุที่เปลี่ยนแปลงซึ่งเปลี่ยนไปซึ่งจะเป็นอย่างที่ฉันคาดไว้

หากใครสามารถแนะนำได้ว่าเหตุใดจึงเกิดขึ้นหรือฉันผิดพลาดตรงไหนที่นี่จะได้รับการชื่นชมอย่างมากขอบคุณมาก!

คำตอบ:


137

ฟังก์ชันการเปรียบเทียบระหว่างค่าเก่าและค่าใหม่กำลังมีปัญหา จะเป็นการดีกว่าที่จะไม่ทำให้สิ่งต่างๆซับซ้อนมากนักเพราะจะเพิ่มความพยายามในการแก้ไขข้อบกพร่องในภายหลัง คุณควรทำให้เรียบง่าย

วิธีที่ดีที่สุดคือสร้าง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-material
Craig van Tonder

2
ยังไงก็ตามขอบคุณที่สละเวลาอธิบายสิ่งนี้มันช่วยให้ฉันได้เรียนรู้เพิ่มเติมเกี่ยวกับ Vue ซึ่งฉันรู้สึกขอบคุณ!
Craig van Tonder

ฉันใช้เวลาพอสมควรในการทำความเข้าใจ แต่คุณพูดถูกจริงๆนี่เป็นสิ่งที่มีเสน่ห์และเป็นวิธีที่ถูกต้องในการทำสิ่งต่างๆหากคุณต้องการหลีกเลี่ยงความสับสนและปัญหาอื่น ๆ :)
Craig van Tonder

1
ฉันก็สังเกตเห็นสิ่งนี้เช่นกันและมีความคิดเดียวกัน แต่สิ่งที่มีอยู่ในวัตถุก็คือดัชนีค่าซึ่งมีค่าตัวรับและตัวตั้งค่าอยู่ที่นั่น แต่ในการเปรียบเทียบมันไม่สนใจพวกเขาเพราะขาดความเข้าใจที่ดีขึ้นฉันคิดว่ามันเป็นเช่นนั้น ไม่มีการประเมินต้นแบบใด ๆ หนึ่งในคำตอบอื่น ๆ ให้เหตุผลว่าทำไมมันถึงใช้งานไม่ได้นั่นเป็นเพราะ newVal และ oldVal เป็นสิ่งเดียวกันมันค่อนข้างซับซ้อน แต่เป็นสิ่งที่ได้รับการแก้ไขในไม่กี่แห่ง แต่อีกคำตอบหนึ่งให้การแก้ไขที่เหมาะสมสำหรับการสร้างที่ง่าย วัตถุที่ไม่เปลี่ยนรูปเพื่อวัตถุประสงค์ในการเปรียบเทียบ
Craig van Tonder

1
ในที่สุดวิธีของคุณจะเข้าใจได้ง่ายกว่าในภาพรวมและให้ความยืดหยุ่นมากขึ้นในแง่ของสิ่งที่มีอยู่เมื่อค่าเปลี่ยนแปลง มันช่วยฉันได้มากที่จะเข้าใจประโยชน์ของการทำให้มันเรียบง่ายใน Vue แต่ติดอยู่กับมันเล็กน้อยอย่างที่คุณเห็นในคำถามอื่นของฉัน ขอบคุณมาก! :)
Craig van Tonder

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

ดู codepen ที่อัปเดต


ดังนั้นเมื่อติดตั้งจะเก็บสำเนาของข้อมูลและใช้สิ่งนี้เพื่อเปรียบเทียบกับมัน น่าสนใจ แต่กรณีการใช้งานของฉันจะซับซ้อนกว่านี้และฉันไม่แน่ใจว่าจะทำงานอย่างไรเมื่อเพิ่มและลบวัตถุออกจากอาร์เรย์ @Quirk ให้ลิงก์ที่ดีเพื่อแก้ปัญหาด้วย แต่ฉันไม่รู้ว่าคุณสามารถใช้ได้vm.$dataขอบคุณ!
Craig van Tonder

ใช่และฉันกำลังอัปเดตหลังจากนาฬิกาโดยเรียกใช้เมธอดอีกครั้งโดยถ้าคุณกลับไปที่ค่าเดิมมันจะติดตามการเปลี่ยนแปลง
Viplock

โอ้ฉันไม่ได้สังเกตว่าการซ่อนอยู่ตรงนั้นสมเหตุสมผลมากและเป็นวิธีที่ซับซ้อนน้อยกว่าในการจัดการกับสิ่งนี้ (ตรงข้ามกับโซลูชันบน github)
Craig van Tonder

และถ้าคุณกำลังเพิ่มหรือลบบางอย่างออกจากอาร์เรย์เดิมเพียงแค่เรียกใช้เมธอดนั้นอีกครั้งและคุณก็พร้อมที่จะใช้วิธีแก้ปัญหาอีกครั้ง
Viplock

1
_.cloneDeep () ช่วยได้มากในกรณีของฉัน ขอบคุณ!! เป็นประโยชน์จริงๆ!
Cristiana Pereira

18

เป็นพฤติกรรมที่กำหนดไว้อย่างดี คุณไม่สามารถรับค่าเก่าสำหรับวัตถุที่กลายพันธุ์ได้ นั่นเป็นเพราะทั้งสองnewValและoldValอ้างถึงวัตถุเดียวกัน Vue จะไม่เก็บสำเนาเก่าของวัตถุที่คุณกลายพันธุ์

หากคุณแทนที่วัตถุด้วยวัตถุอื่น Vue จะให้ข้อมูลอ้างอิงที่ถูกต้องแก่คุณ

อ่านNoteหัวข้อในเอกสาร ( vm.$watch)

เพิ่มเติมเกี่ยวกับเรื่องนี้ที่นี่และที่นี่


3
โอ้หมวกของฉันขอบคุณมาก! นั่นเป็นเรื่องที่ยุ่งยาก ... ฉันคาดหวังว่า val และ oldVal จะแตกต่างกันโดยสิ้นเชิง แต่หลังจากตรวจสอบแล้วฉันเห็นว่ามันเป็นสำเนาของอาร์เรย์ใหม่สองชุด แต่ก่อนหน้านี้ไม่ได้ติดตาม อ่านเพิ่มเติมเล็กน้อยและพบคำถาม SO ที่ยังไม่ได้รับคำตอบเกี่ยวกับความเข้าใจผิดเดียวกัน: stackoverflow.com/questions/35991494/…
Craig van Tonder

5

นี่คือสิ่งที่ฉันใช้เพื่อดูวัตถุอย่างลึกซึ้ง ความต้องการของฉันคือการเฝ้าดูช่องย่อยของวัตถุ

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

ผมเชื่อว่าคุณอาจจะหายไปความเข้าใจใน cavet ที่ซึ่งได้อธิบายไว้ในstackoverflow.com/a/41136186/2110294 เพื่อความชัดเจนนี่ไม่ใช่วิธีแก้ปัญหาสำหรับคำถามและจะไม่ได้ผลอย่างที่คุณคาดหวังในบางสถานการณ์
Craig van Tonder

นี่คือสิ่งที่ฉันกำลังมองหา!. ขอบคุณ
Jaydeep Shil

เหมือนกันตรงนี้เป๊ะ !! ขอบคุณ.
Guntar

5

โซลูชันส่วนประกอบและโซลูชันโคลนลึกมีข้อดี แต่ยังมีปัญหา:

  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.