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/を解決しますが、2つのイベントを発行することによって解決します。

  • 孫からミドルコンポーネントまで
  • 次に、中間コンポーネントから祖父母に再び放出します

この中間イベントを追加することは、反復的で不必要に思えます。私が知らない祖父母に直接放出する方法はありますか?

回答:


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

それ以外の場合は、はい、再発行するか、バスを使用する必要があります。

注:IEを処理するためにdoEventメソッドにコードを追加しました。そのコードは再利用可能な方法で抽出できます。


これはIEでは異なる動作をしますか?... VUEでのブラウザの不一致があった知らなかった
BassMHL

@BassemLhmVueはIEで問題ありません。IEの問題はVueではありません。これはDOMソリューションであり、IEで新しいEvent()を実行することはできません。document.createEvent()を作成する必要があります。必要に応じてIEサポートを追加できます。
バート

1つの単純なケースのためだけにvuexをインストールすることは意味がありません。
AdamOrlov19年

@AdamOrlov私はあなたに同意します。
バート


34

NEW ANSWER(2018年11月更新)

$parent孫コンポーネントのプロパティを活用することで、実際にこれを実行できることを発見しました。

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

はるかにクリーンでシンプル。


21
これは、関係が子->祖父母の場合にのみ機能することに注意してください。子を任意のレベルの深さにネストできる場合、これは機能しませ
qtax 2018

私の答えを参照してくださいstackoverflow.com/a/55650245/841591を@Qtaxからのコメントに反応して
DIGOUT

9
あなたはあなたの大きなプロジェクトでそのようなことが起こることを望まないでしょう。「子」をtransitionまたは他のラッパーコンポーネントに入れると、壊れてしまい、頭に大きな疑問符が残ります。
AdamOrlov19年

1
@AdamOrlov同意します、これは悪い習慣です。Vuexストアを使用してこのようなイベントを処理してください。
Fabian vonEllerts19年

これはもっと簡単です:stackoverflow.com/a/55650245/841591
digout 2010

26

はい、あなたは正しいですイベントは子供から親にのみ行きます。彼らは、例えば子供から祖父母へとそれ以上進まない。

Vueのドキュメント(簡単に)では、親子以外のコミュニケーションのセクションでこの状況に対処しています。

一般的な考え方は、祖父母コンポーネントで、祖父母Vueから小道具を介して子供や孫に渡される空のコンポーネントを作成することです。次に、祖父母はイベントをリッスンし、孫はその「イベントバス」でイベントを発行します。

一部のアプリケーションは、コンポーネントごとのイベントバスの代わりにグローバルイベントバスを使用します。グローバルイベントバスを使用するということは、イベントが異なるコンポーネント間で衝突しないように、一意のイベント名または名前空間を設定する必要があることを意味します。

単純なグローバルイベントバスを実装する方法の例を次に示します。


18

別の解決策は、ルートノードでオン/エミットします。

孫で使用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>


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

2番目:子コンポーネントでイベントを発行します:

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

3番目:親でそのイベントを聞きます:

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対象部品の属性 、私はトンネルにルートにすべての方法を必要としなかった私のユースケースでの焼成から数イベントや多分の貴重なナノ秒のカップルを保存します時間
マッグロウ

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 = thislet 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.