VueJs 2.0 ปล่อยเหตุการณ์จากแกรนด์ลูกไปยังองค์ประกอบแม่ที่ยิ่งใหญ่ของเขา


98

ดูเหมือนว่า Vue.js 2.0 จะไม่ส่งเหตุการณ์จากแกรนด์เด็กไปยังองค์ประกอบหลักของแกรนด์

Vue.component('parent', {
  template: '<div>I am the parent - {{ action }} <child @eventtriggered="performAction"></child></div>',
  data(){
    return {
      action: 'No action'
    }
  },
  methods: {
    performAction() { this.action = 'actionDone' }
  }
})

Vue.component('child', {
  template: '<div>I am the child <grand-child></grand-child></div>'
})

Vue.component('grand-child', {
  template: '<div>I am the grand-child <button @click="doEvent">Do Event</button></div>',
  methods: {
    doEvent() { this.$emit('eventtriggered') }
  }
})

new Vue({
  el: '#app'
})

JsFiddle นี้ช่วยแก้ปัญหาได้https://jsfiddle.net/y5dvkqbd/4/แต่โดยปล่อยสองเหตุการณ์:

  • หนึ่งจากลูกใหญ่ไปจนถึงองค์ประกอบกลาง
  • จากนั้นเปล่งแสงอีกครั้งจากองค์ประกอบกลางไปยังพาเรนต์ใหญ่

การเพิ่มเหตุการณ์ตรงกลางนี้ดูเหมือนจะซ้ำซากและไม่จำเป็น มีวิธีที่จะส่งไปยังผู้ปกครองที่ไม่รู้จักโดยตรงหรือไม่?

คำตอบ:


73

Vue 2.4 แนะนำวิธีการส่งผ่านเหตุการณ์ตามลำดับชั้นโดยใช้ไฟล์ vm.$listeners

จากhttps://vuejs.org/v2/api/#vm-listeners :

มีตัวรับv-onฟังเหตุการณ์ขอบเขตหลัก(ไม่มี.nativeตัวปรับแต่ง) สิ่งนี้สามารถส่งต่อไปยังส่วนประกอบด้านในผ่านv-on="$listeners"- มีประโยชน์เมื่อสร้างส่วนประกอบกระดาษห่อหุ้มแบบโปร่งใส

ดูตัวอย่างด้านล่างโดยใช้v-on="$listeners"ในgrand-childองค์ประกอบในchildเทมเพลต:

Vue.component('parent', {
  template:
    '<div>' +
      '<p>I am the parent. The value is {{displayValue}}.</p>' +
      '<child @toggle-value="toggleValue"></child>' +
    '</div>',
  data() {
    return {
      value: false
    }
  },
  methods: {
    toggleValue() { this.value = !this.value }
  },
  computed: {
    displayValue() {
      return (this.value ? "ON" : "OFF")
    }
  }
})

Vue.component('child', {
  template:
    '<div class="child">' +
      '<p>I am the child. I\'m just a wrapper providing some UI.</p>' +
      '<grand-child v-on="$listeners"></grand-child>' +
    '</div>'
})

Vue.component('grand-child', {
  template:
    '<div class="child">' +
      '<p>I am the grand-child: ' +
        '<button @click="emitToggleEvent">Toggle the value</button>' +
      '</p>' +
    '</div>',
  methods: {
    emitToggleEvent() { this.$emit('toggle-value') }
  }
})

new Vue({
  el: '#app'
})
.child {
  padding: 10px;
  border: 1px solid #ddd;
  background: #f0f0f0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>

<div id="app">
  <parent></parent>
</div>


42

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

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

คำตอบด้านล่างเป็นคำตอบเดิมของฉันสำหรับคำถามนี้และไม่ใช่แนวทางที่ฉันจะใช้ตอนนี้มีประสบการณ์มากขึ้นกับ Vue


นี่เป็นกรณีที่ฉันอาจไม่เห็นด้วยกับตัวเลือกการออกแบบของ Vue และหันมาใช้ DOM

ในgrand-child,

methods: {
    doEvent() { 
        try {
            this.$el.dispatchEvent(new Event("eventtriggered"));
        } catch (e) {
            // handle IE not supporting Event constructor
            var evt = document.createEvent("Event");
            evt.initEvent("eventtriggered", true, false);
            this.$el.dispatchEvent(evt);
        }
    }
}

และในparent,

mounted(){
    this.$el.addEventListener("eventtriggered", () => this.performAction())
}

มิฉะนั้นใช่คุณต้องปล่อยใหม่หรือใช้รถประจำทาง

หมายเหตุ: ฉันเพิ่มโค้ดในวิธี doEvent เพื่อจัดการกับ IE รหัสนั้นสามารถแยกออกมาด้วยวิธีที่ใช้ซ้ำได้


สิ่งนี้ทำงานแตกต่างกันสำหรับ IE? ไม่ทราบว่ามีความคลาดเคลื่อนของเบราว์เซอร์กับ vue ...
BassMHL

@BassemLhm Vue ใช้ได้กับ IE ปัญหาเกี่ยวกับ IE ไม่ใช่ Vue นี่คือโซลูชัน DOM และคุณไม่สามารถทำ Event () ใหม่ใน IE ได้ คุณต้อง document.createEvent () ฉันสามารถเพิ่มการรองรับ IE ได้หากจำเป็น
Bert

ไม่สมเหตุสมผลที่จะติดตั้ง vuex สำหรับกรณีง่ายๆเพียงกรณีเดียว
Adam Orlov

@AdamOrlov ฉันเห็นด้วยกับคุณ
เบิร์ต

วิธีแก้ปัญหาที่ง่ายกว่า: stackoverflow.com/a/55650245/841591
digout

34

คำตอบใหม่ (อัปเดตพฤศจิกายน 2018)

ฉันค้นพบว่าเราสามารถทำได้จริงโดยใช้ประโยชน์จาก$parentคุณสมบัติในองค์ประกอบลูกใหญ่:

this.$parent.$emit("submit", {somekey: somevalue})

สะอาดและเรียบง่ายกว่ามาก


21
โปรดทราบว่าสิ่งนี้ใช้ได้เฉพาะในกรณีที่ความสัมพันธ์เป็นลูก -> ปู่ย่า วิธีนี้ใช้ไม่ได้หากเด็กสามารถซ้อนกันได้ในระดับลึกโดยพลการ
Qtax

ดูคำตอบของฉันstackoverflow.com/a/55650245/841591ในการตอบกลับความคิดเห็นจาก @Qtax
digout

9
คุณไม่ต้องการให้สิ่งนั้นเกิดขึ้นในโครงการใหญ่ของคุณ คุณใส่ 'เด็ก' ไว้ในtransitionส่วนประกอบของกระดาษห่อหุ้มอื่น ๆ หรืออะไรก็ได้และมันจะแตกออกทำให้คุณมีเครื่องหมายคำถามขนาดใหญ่อยู่ในหัว
อดัมออร์ลอฟ

1
@AdamOrlov ฉันเห็นด้วยนี่เป็นการปฏิบัติที่ไม่ดี โปรดจัดการเหตุการณ์เช่นนี้โดยใช้ร้านค้า Vuex
Fabian von Ellerts

สิ่งนี้ง่ายกว่า: stackoverflow.com/a/55650245/841591
digout

26

ใช่คุณถูกต้องตามเหตุการณ์จากเด็กไปสู่ผู้ปกครองเท่านั้น พวกเขาไม่ไปไกลกว่านี้เช่นตั้งแต่เด็กจนถึงปู่ย่าตายาย

The Vue เอกสาร (สั้น ๆ ) ที่อยู่ในสถานการณ์เช่นนี้ในองค์กรไม่แสวงหาสื่อสารผู้ปกครองเด็กส่วน

แนวคิดทั่วไปคือในองค์ประกอบปู่ย่าคุณสร้างว่างเปล่า Vueส่วนประกอบที่ส่งต่อจากปู่ย่าตายายไปยังลูก ๆ และลูกหลานผ่านอุปกรณ์ประกอบฉาก จากนั้นปู่ย่าก็ฟังเหตุการณ์และหลาน ๆ ก็ส่งเสียงอีเวนต์บน "รถบัสเหตุการณ์" นั้น

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

นี่คือตัวอย่างของวิธีการใช้รถบัสเหตุการณ์ง่าย ๆ ทั่วโลก


18

โซลูชันอื่นจะเปิด / ปล่อยที่โหนดรูท :

ใช้vm.$root.$emitในgrand-childแล้วใช้vm.$root.$onที่บรรพบุรุษ (หรือทุกที่ที่คุณต้องการ)

อัปเดต : บางครั้งคุณต้องการปิดการใช้งาน Listener ในบางสถานการณ์ให้ใช้vm. $ off (ตัวอย่างเช่น vm.$root.off('event-name')inside lifecycle hook = beforeDestroy )

Vue.component('parent', {
  template: '<div><button @click="toggleEventListener()">Listener is {{eventEnable ? "On" : "Off"}}</button>I am the parent - {{ action }} <child @eventtriggered="performAction"></child></div>',
  data(){
    return {
      action: 1,
      eventEnable: false
    }
  },
  created: function () {
    this.addEventListener()
  },
  beforeDestroy: function () {
    this.removeEventListener()
  },
  methods: {
    performAction() { this.action += 1 },
    toggleEventListener: function () {
      if (this.eventEnable) {
        this.removeEventListener()
      } else {
        this.addEventListener()
      }
    },
    addEventListener: function () {
      this.$root.$on('eventtriggered1', () => {
        this.performAction()
      })
      this.eventEnable = true
    },
    removeEventListener: function () {
      this.$root.$off('eventtriggered1')
      this.eventEnable = false
    }
  }
})

Vue.component('child', {
  template: '<div>I am the child <grand-child @eventtriggered="doEvent"></grand-child></div>',
  methods: {
    doEvent() { 
    	//this.$emit('eventtriggered') 
    }
  }
})

Vue.component('grand-child', {
  template: '<div>I am the grand-child <button @click="doEvent">Emit Event</button></div>',
  methods: {
    doEvent() { this.$root.$emit('eventtriggered1') }
  }
})

new Vue({
  el: '#app'
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>

<div id="app">
  <parent></parent>
</div>


17

หากคุณต้องการมีความยืดหยุ่นและเพียงแค่ถ่ายทอดกิจกรรมไปยังผู้ปกครองและผู้ปกครองของพวกเขาซ้ำ ๆ จนถึงรูทคุณสามารถทำสิ่งต่อไปนี้

let vm = this.$parent

while(vm) {
    vm.$emit('submit')
    vm = vm.$parent
}

4

นี่เป็นกรณีเดียวที่ฉันใช้อีเว้นท์บัส !! สำหรับการส่งผ่านข้อมูลจากลูกที่ซ้อนกันลึกไปยังผู้ปกครองโดยตรงไม่ใช่การสื่อสาร

ขั้นแรก : สร้างไฟล์ js (ฉันตั้งชื่อมันว่า eventbus.js) ด้วยเนื้อหานี้:

import Vue from 'vue'    
Vue.prototype.$event = new Vue()

ประการที่สอง : ในองค์ประกอบลูกของคุณปล่อยเหตุการณ์:

this.$event.$emit('event_name', 'data to pass')

สาม : ในผู้ปกครองฟังเหตุการณ์นั้น:

this.$event.$on('event_name', (data) => {
  console.log(data)
})

หมายเหตุ:หากคุณไม่ต้องการให้เกิดเหตุการณ์นั้นอีกต่อไปโปรดยกเลิกการลงทะเบียน:

this.$event.$off('event_name')

ข้อมูล: ไม่จำเป็นต้องอ่านความคิดเห็นส่วนตัวด้านล่าง

ฉันไม่ชอบใช้ vuex สำหรับการสื่อสารระดับแกรนด์กับผู้ปกครองระดับใหญ่ (หรือระดับการสื่อสารที่ใกล้เคียงกัน)

ใน vue.js สำหรับการส่งผ่านข้อมูลจากแกรนด์แกรนด์แม่เพื่อลูกคุณสามารถใช้ให้ / ฉีด แต่ไม่มีสิ่งที่คล้ายกันสำหรับสิ่งที่ตรงกันข้าม (grand-child to grand-parent) ดังนั้นฉันจึงใช้ event bus ทุกครั้งที่ต้องสื่อสารแบบนั้น


2

ฉันได้ทำการมิกซ์อินสั้น ๆ ตามคำตอบของ @digout คุณต้องการวางไว้ก่อนการเริ่มต้นอินสแตนซ์ Vue ของคุณ (Vue ... ใหม่) เพื่อใช้ทั่วโลกในโปรเจ็กต์ คุณสามารถใช้งานได้เหมือนกับเหตุการณ์ปกติ

Vue.mixin({
  methods: {
    $propagatedEmit: function (event, payload) {
      let vm = this.$parent;
      while (vm) {
        vm.$emit(event, payload);
        vm = vm.$parent;
      }
    }
  }
})

โซลูชันนี้เป็นสิ่งที่ฉันใช้สำหรับการติดตั้ง แต่ฉันได้เพิ่มพารามิเตอร์เพิ่มเติมtargetRefที่หยุดการเผยแพร่ในองค์ประกอบที่คุณกำหนดเป้าหมาย whileสภาพแล้วจะรวมถึง&& vm.$refs[targetRef]- คุณยังจะต้องรวมถึงการที่refแอตทริบิวต์ในองค์ประกอบที่กำหนดเป้าหมาย ในกรณีที่การใช้งานของฉันฉันไม่จำเป็นต้องอุโมงค์ไปตลอดทางจนถึงรากบันทึกเหตุการณ์ที่เกิดขึ้นไม่กี่จากการยิงและบางทีคู่ของนาโนวินาทีที่มีค่าของ เวลา
mcgraw

2

ส่วนประกอบ VueJS 2 มีไฟล์ $parentคุณสมบัติที่มีองค์ประกอบหลัก

องค์ประกอบหลักนั้นรวมถึงองค์ประกอบของตัวเองด้วย $parentคุณสมบัติด้วย

จากนั้นการเข้าถึงองค์ประกอบ "ปู่ย่าตายาย" เป็นเรื่องของการเข้าถึงองค์ประกอบ "ผู้ปกครองของผู้ปกครอง":

this.$parent["$parent"].$emit("myevent", { data: 123 });

อย่างไรก็ตามนี่เป็นเรื่องที่ยุ่งยากและฉันขอแนะนำให้ใช้ผู้จัดการระดับโลกเช่น Vuex หรือเครื่องมือที่คล้ายกันตามที่เจ้าหน้าที่ตอบกลับอื่น ๆ กล่าว


1

คำตอบของ @kubaklam และ @ digout นี่คือสิ่งที่ฉันใช้เพื่อหลีกเลี่ยงการเปล่งเสียงในทุกองค์ประกอบของผู้ปกครองระหว่างปู่ - ลูกกับปู่ย่าตายาย (อาจอยู่ห่างไกล):

{
  methods: {
    tunnelEmit (event, ...payload) {
      let vm = this
      while (vm && !vm.$listeners[event]) {
        vm = vm.$parent
      }
      if (!vm) return console.error(`no target listener for event "${event}"`)
      vm.$emit(event, ...payload)
    }
  }
}

เมื่อสร้างองค์ประกอบที่มีลูกหลานที่อยู่ห่างไกลซึ่งคุณไม่ต้องการให้ส่วนประกอบจำนวนมาก / ใด ๆ เชื่อมโยงกับร้านค้า แต่ต้องการให้องค์ประกอบรากทำหน้าที่เป็นร้านค้า / แหล่งที่มาของความจริงสิ่งนี้ใช้ได้ดีทีเดียว สิ่งนี้คล้ายกับการดำเนินการตามปรัชญาของ Ember ข้อเสียคือถ้าคุณต้องการฟังเหตุการณ์นั้นกับผู้ปกครองทุกคนในระหว่างนั้นสิ่งนี้จะไม่ได้ผล แต่คุณสามารถใช้ $ propogateEmit ตามคำตอบด้านบนโดย @kubaklam

แก้ไข: ควรตั้งค่า vm เริ่มต้นให้กับคอมโพเนนต์ไม่ใช่พาเรนต์ของคอมโพเนนต์ เช่นlet vm = thisและไม่let vm = this.$parent


0

ฉันขุดวิธีจัดการโดยการสร้างคลาสที่เชื่อมโยงกับหน้าต่างและลดความซับซ้อนของการตั้งค่าการออกอากาศ / ฟังเพื่อให้ทำงานได้ทุกที่ในแอป Vue

window.Event = new class {

    constructor() {
        this.vue = new Vue();
    }

    fire(event, data = null) {
        this.vue.$emit(event, data);
    }

    listen() {
        this.vue.$on(event, callback);  
    }

}

ตอนนี้คุณสามารถยิง / ออกอากาศ / อะไรก็ได้จากทุกที่โดยโทร:

Event.fire('do-the-thing');

... และคุณสามารถฟังพ่อแม่ปู่ย่าตายายสิ่งที่คุณต้องการโดยโทร:

Event.listen('do-the-thing', () => {
    alert('Doing the thing!');
});

1
ฉันขอแนะนำเป็นอย่างยิ่งว่าอย่าแนบคุณสมบัติแบบสุ่มกับวัตถุหน้าต่างเนื่องจากง่ายมากที่จะเขียนทับคุณสมบัติที่มีอยู่หรือขัดแย้งกับไลบรารีของบุคคลที่สามที่มีอยู่ แต่ใครก็ตามที่ใช้ Vue เพื่อแก้ปัญหานี้ควรใช้คำตอบของ @roli roli แทน
HMilbradt

1
ฉันไม่แน่ใจว่าเข้าใจทั้งหมดหรือเห็นด้วยกับข้อกังวลนี้ การเชื่อมโยงกับต้นแบบเป็นวิธีการที่ดี แต่การผูกกับหน้าต่างก็เหมือนกับว่าถ้าไม่มากไปกว่านั้นเป็นเรื่องธรรมดาและอาจเป็นวิธีจัดการที่เป็นมาตรฐานมากกว่า คุณตั้งชื่อคุณสมบัติดังนั้นจึงเป็นเรื่องง่ายที่จะหลีกเลี่ยงความขัดแย้งในการตั้งชื่อ medium.com/@amitavroy7/... stackoverflow.com/questions/15008464/... นี่ก็เป็นโซลูชั่นที่นำเสนอวิธีที่เจฟฟ์ใช้ใน Laracasts laracasts.com/series/learn-vue-2-step-by-step/episodes/13
fylzero
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.