อะไรคือวิธีที่ถูกต้องในการส่งอุปกรณ์ประกอบฉากเป็นข้อมูลเริ่มต้นใน Vue.js 2


101

ดังนั้นฉันต้องการส่งอุปกรณ์ประกอบฉากไปยังส่วนประกอบ Vue แต่ฉันคาดว่าอุปกรณ์ประกอบฉากเหล่านี้จะเปลี่ยนแปลงในอนาคตจากภายในส่วนประกอบนั้นเช่นเมื่อฉันอัปเดตส่วนประกอบ Vue นั้นจากภายในโดยใช้ AJAX ดังนั้นจึงมีไว้สำหรับการเริ่มต้นส่วนประกอบเท่านั้น

cars-listองค์ประกอบองค์ประกอบ Vue ของฉันที่ฉันส่งผ่านอุปกรณ์ประกอบฉากที่มีคุณสมบัติเริ่มต้นไปที่single-car:

// cars-list.vue

<script>
    export default {
        data: function() {
            return {
                cars: [
                    {
                        color: 'red',
                        maxSpeed: 200,
                    },
                    {
                        color: 'blue',
                        maxSpeed: 195,
                    },
                ]
            }
        },
    }
</script>

<template>
    <div>
        <template v-for="car in cars">
            <single-car :initial-properties="car"></single-car>
        </template>
    </div>
</template>

วิธีที่ฉันทำตอนนี้คือภายในsingle-carส่วนประกอบของฉันฉันกำหนด this.initialPropertiesให้this.data.propertiesกับcreated()ตะขอเริ่มต้น และทำงานและมีปฏิกิริยา

// single-car.vue

<script>
    export default {
        data: function() {
            return {
                properties: {},
            }
        },
        created: function(){
            this.data.properties = this.initialProperties;
        },
    }
</script>

<template>
    <div>Car is in {{properties.color}} and has a max speed of {{properties.maxSpeed}}</div>
</template>

แต่ปัญหาของฉันคือฉันไม่รู้ว่าเป็นวิธีที่ถูกต้องหรือไม่? มันจะทำให้ฉันมีปัญหาระหว่างทางบ้างไหม? หรือมีวิธีที่ดีกว่านี้ไหม


13
นี่คือสิ่งที่ทำให้เกิดความสับสนมากที่สุดเกี่ยวกับ Vue ในความคิดของฉัน: ทุกdataถูกtwo-wayผูกไว้ แต่คุณไม่สามารถผ่านdataในส่วนของคุณผ่านpropsแต่คุณไม่สามารถเปลี่ยนที่ได้รับpropsหรือแปลงไปprops dataแล้วไง? สิ่งหนึ่งที่ฉันได้เรียนรู้คือคุณควรผ่านpropsและกระตุ้นเหตุการณ์ต่างๆ นั่นคือถ้าส่วนประกอบต้องการเปลี่ยนองค์ประกอบที่propsได้รับควรเรียกเหตุการณ์และ "แสดงผลใหม่" แต่คุณก็เหลือแค่การone-wayผูกเหมือน React และฉันไม่เห็นการใช้งานในdataตอนนั้น ค่อนข้างสับสน
André Pena

1
ข้อมูลมีวัตถุประสงค์หลักเพื่อการใช้งานส่วนบุคคลขององค์ประกอบ ทุกสิ่งที่วางไว้ภายในบริบทของส่วนประกอบนั้นมีปฏิกิริยาและสามารถผูกมัดได้ แนวคิดเกี่ยวกับอุปกรณ์ประกอบฉากคือการส่งผ่านค่าไปยังส่วนประกอบ แต่ป้องกันไม่ให้คอมโพเนนต์แนะนำการเปลี่ยนแปลงสถานะในพาเรนต์โดยการเปลี่ยนค่าที่ผ่าน เป็นการดีกว่าที่จะทำให้ชัดเจนในเหตุการณ์ตามที่คุณระบุไว้ นี่คือการเปลี่ยนแปลงปรัชญาจาก Vue 1.0 เป็น 2.0
David K. Hess

วันนี้ฉันได้ลองตั้งกระทู้ที่นี่แล้ว: forum.vuejs.org/t/…
bgraves

คำตอบ:


109

ขอบคุณhttps://github.com/vuejs/vuejs.org/pull/567ตอนนี้ฉันรู้คำตอบแล้ว

วิธีที่ 1

ส่งเสาหลักไปยังข้อมูลโดยตรง เช่นเดียวกับตัวอย่างในเอกสารที่อัปเดต:

props: ['initialCounter'],
data: function () {
    return {
        counter: this.initialCounter
    }
}

แต่โปรดทราบว่าถ้า prop ที่ส่งผ่านเป็นอ็อบเจ็กต์หรืออาร์เรย์ที่ใช้ในสถานะคอมโพเนนต์พาเรนต์การแก้ไขใด ๆ กับ prop นั้นจะส่งผลให้สถานะองค์ประกอบพาเรนต์เปลี่ยนไป

คำเตือน : ไม่แนะนำให้ใช้วิธีนี้ มันจะทำให้ส่วนประกอบของคุณไม่สามารถคาดเดาได้ หากคุณต้องการตั้งค่าข้อมูลพาเรนต์จากคอมโพเนนต์ย่อยให้ใช้การจัดการสถานะเช่น Vuex หรือใช้ "v-model" https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components

วิธีที่ 2

หากเสาเริ่มต้นของคุณเป็นวัตถุหรืออาร์เรย์และหากคุณไม่ต้องการให้การเปลี่ยนแปลงในสถานะลูกแพร่กระจายไปยังสถานะแม่ให้ใช้เช่นVue.util.extend[1] เพื่อสร้างสำเนาของอุปกรณ์ประกอบฉากแทนที่จะชี้ไปที่ข้อมูลลูกโดยตรงเช่นนี้:

props: ['initialCounter'],
data: function () {
    return {
        counter: Vue.util.extend({}, this.initialCounter)
    }
}

วิธีที่ 3

คุณยังสามารถใช้ตัวดำเนินการกระจายเพื่อโคลนอุปกรณ์ประกอบฉาก รายละเอียดเพิ่มเติมในคำตอบของ Igor: https://stackoverflow.com/a/51911118/3143704

แต่มีในใจว่าผู้ประกอบการแพร่กระจายยังไม่ได้รับการสนับสนุนในเบราว์เซอร์รุ่นเก่าและเข้ากันได้ดีกว่าที่คุณจะต้อง transpile babelเช่นรหัสโดยใช้

เชิงอรรถ

[1] โปรดทราบว่านี่เป็นยูทิลิตี้ Vue ภายในและอาจมีการเปลี่ยนแปลงตามเวอร์ชันใหม่ คุณอาจต้องการใช้วิธีอื่นในการคัดลอกเสานั้นโปรดดูที่ " ฉันจะโคลนวัตถุ JavaScript อย่างถูกต้องได้อย่างไร "

ซอของฉันที่ฉันกำลังทดสอบ: https://jsfiddle.net/sm4kx7p9/3/


1
ดังนั้นการเปลี่ยนแปลงที่เผยแพร่ไปยังองค์ประกอบหลักเป็นเรื่องปกติหรือไม่?
อิก

1
@igor โดยส่วนตัวแล้วฉันจะพยายามหลีกเลี่ยงให้มากที่สุด มันจะทำให้ส่วนประกอบของคุณไม่สามารถคาดเดาได้ ฉันคิดว่าวิธีที่ดีกว่าในการรับการเปลี่ยนแปลงในองค์ประกอบหลักคือการใช้วิธี "v-model" ที่คุณ $ ปล่อยการเปลี่ยนแปลงจากองค์ประกอบลูกของคุณเป็นองค์ประกอบหลัก vuejs.org/v2/guide/components.html#Using-v-model-on-Components
Dominik Serafin

ใช่ แต่วัตถุที่อยู่ลึกล่ะ? ฉันควรคัดลอกแบบลึกไหม ฉันควรออกแบบส่วนประกอบใหม่เพื่อไม่ให้ใช้วัตถุล้ำลึกหรือไม่? เช่น prop with: { ok: 1, notOk: { meh: 2 } }หากตั้งค่าเป็นข้อมูลภายในด้วยวิธีโคลนก่อนหน้านี้จะยังคงเปลี่ยน propnotOk.meh
Nikso

2
ขอบคุณสำหรับคำถามและคำตอบที่ยอดเยี่ยม @DominikSerafin นี่แสดงให้เห็นถึงการรักษา Achilles ของ Vue.js อย่างสมบูรณ์แบบ ในฐานะที่เป็นเฟรมเวิร์กจะจัดการกับองค์ประกอบได้ดี แต่การส่งผ่านข้อมูลระหว่างคอมพ์ถือเป็น PITA ที่สำคัญ เพียงแค่มองไปที่ฝันร้ายการตั้งชื่อที่สร้างขึ้น ตอนนี้ฉันต้องขึ้นต้นอุปกรณ์ประกอบฉากทั้งหมดinitialเพื่อให้สามารถใช้ภายในคอมพ์ของฉันได้ มันจะสะอาดกว่านี้มากถ้าเราสามารถส่ง prop ที่เรียกdataไปยังคอมพ์ใด ๆ และให้มันเผยแพร่ข้อมูลที่แปลเป็นภาษาท้องถิ่นโดยอัตโนมัติโดยปราศจากinitialPropNameความบ้าคลั่งนี้
AJB

1
@DominikSerafin ฉันได้ยินสิ่งที่คุณพูดและคุณไม่ผิด แต่เขียนแอปสร้างฟอร์มโดยใช้ Vue.js แล้วคุณจะเห็นว่าฉันหมายถึงอะไร จริงๆแล้วฉันกำลังอยู่ระหว่างการเปลี่ยนเทคนิคการแชร์ข้อมูลจากprop --> comp.dataเป็นการใช้ Vue-Stash สำหรับข้อมูลทั้งหมด (หรือ Vuex ก็ใช้ได้เช่นกัน) แต่ตอนนี้ฉันแค่ตีกลับข้อมูลของฉันออกจากwindowออบเจ็กต์อย่างที่ฉันเคยทำมาก่อนเฟรมเวิร์ก JS "สมัยใหม่" กำหนดความคิดเห็นของพวกเขากับฉัน ดังนั้นจึงทำให้เกิดคำถาม: อะไรคือจุดของคุณสมบัติ comp.data ทั้งหมด?
AJB

29

ร่วมกับคำตอบของ @ dominik-serafin:

ในกรณีที่คุณกำลังส่งผ่านวัตถุคุณสามารถโคลนได้อย่างง่ายดายโดยใช้ตัวดำเนินการกระจาย (ไวยากรณ์ ES6):

 props: {
   record: {
     type: Object,
     required: true
   }
 },

data () { // opt. 1
  return {
    recordLocal: {...this.record}
  }
},

computed: { // opt. 2
  recordLocal () {
    return {...this.record}
  }
},

แต่ที่สำคัญที่สุดคืออย่าลืมเลือกใช้ 2 ในกรณีที่คุณส่งค่าจากการคำนวณหรือมากกว่าค่าอะซิงโครนัส มิฉะนั้นค่าท้องถิ่นจะไม่อัปเดต

การสาธิต:

Vue.component('card', { 
  template: '#app2',
  props: {
    test1: null,
    test2: null
  },
  data () { // opt. 1
    return {
      test1AsData: {...this.test1}
    }
  },
  computed: { // opt. 2
    test2AsComputed () {
      return {...this.test2}
    }
  }
})

new Vue({
  el: "#app1",
  data () {
    return {
      test1: {1: 'will not update'},
      test2: {2: 'will update after 1 second'}
    }
  },
  mounted () {
    setTimeout(() => {
      this.test1 = {1: 'updated!'}
      this.test2 = {2: 'updated!'}
    }, 1000)
  }
})
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js"></script>

<div id="app1">
  <card :test1="test1" :test2="test2"></card>
</div>

<template id="app2">
  <div>
    test1 as data: {{test1AsData}}
    <hr />
    test2 as computed: {{test2AsComputed}}
  </div>
</template>

https://jsfiddle.net/nomikos3/eywraw8t/281070/


เฮ้ @ igor-parra ดูเหมือนว่าคุณจะส่งคำตอบซ้ำกัน และอาจเป็นการดีที่จะกล่าวถึงว่าตัวดำเนินการกระจายไม่ได้รับการสนับสนุนในทุกเบราว์เซอร์และอาจต้องใช้ขั้นตอนการเลื่อนเพื่อให้เข้ากันได้ดีขึ้น ไม่งั้นจะดูดีมาก - ขอบคุณที่เพิ่มวิธีอื่นนี้!
Dominik Serafin

@ dominik-serafin ใช่คุณพูดถูกฉันเพิ่มการอ้างอิงถึง ES6 ในแอปพลิเคชั่นที่ใช้ webpack มันง่ายมากที่จะมี babel ที่กำหนดค่าไว้ล่วงหน้าเพื่อใช้จาวาสคริปต์ที่ทันสมัยอย่างเต็มที่
Igor Parra

recordLocal: [...this.record](ในเร็กคอร์ดกรณีของฉันคืออาร์เรย์) ไม่ได้ผลสำหรับฉัน - หลังจากโหลดและตรวจสอบส่วนประกอบ vue recordมีรายการและrecordLocalเป็นอาร์เรย์ว่าง
Christian

เมื่อฉันใช้มันแม้ว่าคุณสมบัติ COMPUTED มันจะไปได้ดีสำหรับฉันเมื่อฉันพยายามตั้งค่าภายในข้อมูลมันไม่ทำงาน = (... แต่ฉันใช้มันcomputed
felipe muner

ใส่ใจ. ตัวดำเนินการการแพร่กระจายเป็นเพียงโคลนนิ่งตื้นดังนั้นสำหรับวัตถุที่มีวัตถุหรืออาร์เรย์คุณจะยังคงคัดลอกตัวชี้แทนที่จะได้รับสำเนาใหม่ ฉันใช้ cloneDeep จาก lodash
Cindy Conway

14

ฉันเชื่อว่าคุณทำถูกต้องเพราะเป็นสิ่งที่ระบุไว้ในเอกสาร

กำหนดคุณสมบัติข้อมูลโลคัลที่ใช้ค่าเริ่มต้นของ prop เป็นค่าเริ่มต้น

https://vuejs.org/guide/components.html#One-Way-Data-Flow


4
สำหรับอุปกรณ์ประกอบฉากแบบ pass-by-value นั้นใช้ได้ ในกรณีนี้เป็นวัตถุดังนั้นการเปลี่ยนแปลงจะมีผลต่อพาเรนต์ จากหน้านั้น: "โปรดทราบว่าอ็อบเจ็กต์และอาร์เรย์ใน JavaScript ถูกส่งผ่านโดยการอ้างอิงดังนั้นหาก prop เป็นอาร์เรย์หรืออ็อบเจ็กต์การกลายพันธุ์อ็อบเจ็กต์หรืออาร์เรย์ภายในองค์ประกอบลูกจะส่งผลต่อสถานะพาเรนต์"
Jake

สิ่งนี้ใช้ได้ผลสำหรับฉันและมีการบันทึกไว้อย่างชัดเจน บรรทัดนี้ในคำตอบข้างต้นขัดแย้งกับเอกสาร vue เท่าที่ฉันสามารถบอกได้ว่า "คำเตือน: ไม่แนะนำให้ใช้วิธีนี้จะทำให้ส่วนประกอบของคุณไม่สามารถคาดเดาได้หากคุณต้องการตั้งค่าข้อมูลหลักจากส่วนประกอบย่อยให้ใช้การจัดการสถานะเช่น Vuex หรือใช้ "v-model". "
Nathaniel Rink

2
หลังจากผ่านไป 4 ปีและหลายร้อยชั่วโมงของเส้นโค้งการเรียนรู้ฉันก็กลับมาทันทีที่ฉันเริ่มต้นเพียงแค่เด้งตัวแปรออกจากwindowวัตถุ
AJB
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.