VueJs 2.0은 손자에서 손자 부모 구성 요소로 이벤트를 방출합니다.


87

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/ 문제를 해결 하지만 두 가지 이벤트를 내 보냅니다 .

  • 손자에서 중간 구성 요소까지 하나
  • 그런 다음 중간 구성 요소에서 그랜드 부모로 다시 방출

이 중간 이벤트를 추가하는 것은 반복적이고 불필요 해 보입니다. 내가 알지 못하는 조부모에게 직접 방출하는 방법이 있습니까?

답변:


64

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>


나는 이것이 vuex / bus / root를 도입하지 않고 가장 깨끗한 방법이라는 것을 알았습니다. 이것은 모든 emit 함수를 위쪽으로 전달합니다. v-on = "$ listeners"전에 @ someEmit = "someEmitHandler"를 사용하여 어린이 방출을 계속 활용할 수 있습니다.
Mathias Haugsbø

39

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

그렇지 않으면 예, 다시 내보내거나 버스를 사용해야합니다.

참고 : IE를 처리하기 위해 doEvent 메서드에 코드를 추가했습니다. 그 코드는 재사용 가능한 방식으로 추출 될 수 있습니다.


이것은 IE에서 다르게 작동합니까? ... VUE와 브라우저 차이가 있었다 인식하지 않았다
BassMHL

@BassemLhm Vue는 IE에서 괜찮습니다. IE의 문제는 Vue가 아닙니다. 이것은 DOM 솔루션이며 IE에서 new Event ()를 수행 할 수 없습니다. document.createEvent ()해야합니다. 필요한 경우 IE 지원을 추가 할 수 있습니다.
Bert

간단한 경우에만 vuex를 설치하는 것은 의미가 없습니다.
Adam Orlov

@AdamOrlov 나는 당신과 동의합니다.
Bert

더 간단한 솔루션 : stackoverflow.com/a/55650245/841591
digout

28

NEW ANSWER (2018 년 11 월 업데이트)

저는 우리가 $parent그랜드 하위 구성 요소 의 속성 을 활용하여 실제로이 작업을 수행 할 수 있음을 발견했습니다 .

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

훨씬 더 깨끗하고 간단합니다.


15
이것은 관계가 자식-> 조부모 인 경우에만 작동합니다. 자식이 임의 수준 깊이 중첩 될 수있는 경우에는 작동 하지 않습니다 .
Qtax

@Qtax의 의견에 대한 응답으로 내 답변 stackoverflow.com/a/55650245/841591 참조
digout

7
큰 프로젝트에서 그런 일이 일어나기를 원하지 않습니다. '자식'을 transition다른 래퍼 구성 요소 에 넣으면 깨져 머리에 큰 물음표가 남습니다.
Adam Orlov

@AdamOrlov 동의합니다, 이것은 나쁜 습관입니다. Vuex 스토어를 사용하여 이와 같은 이벤트를 처리하십시오.
Fabian von Ellerts


26

네, 맞습니다 이벤트는 아이에서 부모로만 이동합니다. 그들은 예를 들어 아이에서 조부모로 더 나아 가지 않습니다.

Vue 문서 (간략히)는 비 부모-자녀 통신 섹션 에서이 상황을 다룹니다.

일반적인 아이디어는 조부모 구성 요소 Vue에서 소품을 통해 조부모에서 자식 및 손자에게 전달 되는 빈 구성 요소를 만드는 것입니다. 그런 다음 조부모는 이벤트를 듣고 손자는 해당 "이벤트 버스"에서 이벤트를 내 보냅니다.

일부 애플리케이션은 구성 요소 별 이벤트 버스 대신 글로벌 이벤트 버스를 사용합니다. 전역 이벤트 버스를 사용한다는 것은 이벤트가 서로 다른 구성 요소간에 충돌하지 않도록 고유 한 이벤트 이름이나 네임 스페이스가 필요하다는 것을 의미합니다.

다음은 간단한 전역 이벤트 버스를 구현하는 방법 의 예입니다 .


16

또 다른 솔루션은 루트 노드 에서 on / emit됩니다 .

용도 vm.$root.$emit그랜드 아이 , 다음 사용 vm.$root.$on조상에서 (또는 어디서나 싶습니다).

업데이트 됨 : 특정 상황에서 리스너를 비활성화하고 싶을 때가 있습니다 . 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>


나에게 가장 우아한 해결책.
Ovilia

좋은 해결책! 이것은 주로 Vue 구성 요소가있는 서버 측 애플리케이션 (Laravel)에서 잘 작동했습니다 (Vuex 레이어를 도입하고 싶지 않았습니다). 내 특정 문제는 양식 구성 요소로 Buefy 모달을 트리거하는 사용자 정의 버튼 구성 요소였습니다. 양식이 제출 된 후 버튼을 비활성화하고 싶었지만 Modal이 양식의 직접적인 부모이기 때문에 양식에서 생성 된 사용자 지정 이벤트가 버튼에 도달하지 못했습니다.
Richard

이것은 파괴시 리스너를 파괴하지 않습니다. 그 확인은? 그 논리를 추가 할 수 있다면 좋을 것이므로 그 방법도 알고 있습니다.
mesqueeb 19

14

유연하고 단순히 모든 부모와 부모에게 루트까지 재귀 적으로 이벤트를 브로드 캐스트하려면 다음과 같이 할 수 있습니다.

let vm = this.$parent

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

1
이것이 정답입니다. 내 코드의 여러 위치에서이를 수행하는 유틸리티 함수를 만들었습니다. propagateEvent (component, eventName, value) {while (component) {component. $ emit (eventName, value); component = component. $ parent; }}
ccleve

2
이것은 사용하기 매우 간단하고 읽기 쉬운 우아한 대답입니다. 그리고 추가 수준의 포장을 추가해도 깨지지 않습니다.
Dean

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에서 조부모에서 손자에게 데이터를 전달하기 위해 provide / inject를 사용할 수 있습니다 . 그러나 반대되는 것과 비슷한 것이 없습니다. (손자부터 조부모까지) 그래서 그런 소통이 필요할 때마다 이벤트 버스를 이용합니다.


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의 데이터 다운 액션 업 철학과 유사합니다. 단점은 중간에있는 모든 부모에서 해당 이벤트를 수신하려면 작동하지 않는다는 것입니다. 그러나 @kubaklam의 위 답변에서와 같이 $ propogateEmit을 사용할 수 있습니다.

편집 : 초기 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
기존 속성을 덮어 쓰거나 기존 타사 라이브러리와 충돌하기가 매우 쉽기 때문에 임의 속성을 창 개체에 연결 하지 않는 것이 좋습니다 . 대신,이 문제를 해결하기 위해 뷰를 사용하는 사람이 대신 @roli roli의 대답 사용해야합니다
HMilbradt

1
이 문제를 완전히 이해하거나 동의하는지 잘 모르겠습니다. 프로토 타입에 바인딩하는 것은 좋은 접근 방식이지만 창에 바인딩하는 것은 훨씬 더 일반적이지는 않지만이를 처리하는 더 표준적인 방법과 같습니다. 속성의 이름을 지정하므로 이름 충돌을 피하는 것이 간단합니다. medium.com/@amitavroy7/… stackoverflow.com/questions/15008464/… 이것은 또한 Jeff Way가 Laracasts에서 사용하는 제안 된 솔루션입니다. laracasts.com/series/learn-vue-2-step-by-step/episodes/13
fylzero
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.