web-dev-qa-db-ja.com

VueJs 2.0の兄弟コンポーネント間の通信

Vuejs 2.0では、model.sync非推奨 になります。

では、 vuejs 2. の兄弟コンポーネント間で通信する適切な方法は何ですか?

アイデア Vueで2が兄弟通信を行うことですストアまたはイベントバスを使用して

evan によると:

また、「コンポーネント間でデータを渡す」ことは一般的に悪い考えです。最終的にはデータフローが追跡できなくなり、デバッグが非常に難しくなるためです。

データの一部を複数のコンポーネントで共有する必要がある場合は、 グローバルストア または Vuex を選択してください。

[ ディスカッションへのリンク ]

そして:

.onceおよび.syncは非推奨です。小道具は常に一方通行です。親スコープで副作用を生成するには、コンポーネントは暗黙的なバインディングに依存するのではなく、明示的にイベントをemitする必要があります。

(つまり、彼 提案$emit$onを使用することです)

私が心配しているのは:

  • storeeventにはそれぞれグローバルな可視性があります(間違っている場合は修正してください)。
  • マイナーなコミュニケーションごとに新しいストアを作成するのは大変です。

私が欲しいのはscopeどういうわけかeventsまたはstores兄弟コンポーネントの可視性です。または、おそらく私はその考えを理解しませんでした。

だから、どのように正しい方法で通信しますか?

83
Sergei Panfilov

Vue 2.0では、 documentation で示されているようにeventHubメカニズムを使用しています。

  1. 集中型イベントハブを定義します。

    const eventHub = new Vue() // Single event hub
    
    // Distribute to components using global mixin
    Vue.mixin({
        data: function () {
            return {
                eventHub: eventHub
            }
        }
    })
    
  2. コンポーネントでイベントを発行できます

    this.eventHub.$emit('update', data)
    
  3. そして聞くために

    this.eventHub.$on('update', data => {
    // do your thing
    })
    

Updateより単純なソリューションを説明する @ alex のanwerを参照してください。

73
kakoni

さらに短くして、 rootVueインスタンスをグローバルイベントハブとして使用することもできます。

コンポーネント1:

this.$root.$emit('eventing', data);

コンポーネント2:

mounted() {
    this.$root.$on('eventing', data => {
        console.log(data);
    });
}
91
Alex

これは古い質問ですが、他のコミュニケーションチャネルを公開し、アプリとコミュニケーションをより高い視点から表示する方法を知りたかったのです。


通信タイプ

Vueアプリケーション(または実際、コンポーネントベースのアプリケーション)を設計する際に最初に理解することは、処理する懸念に依存するさまざまな通信タイプがあり、独自の通信チャネルが必要であることです。 。

ビジネスロジック:は、アプリとその目標に固有のすべてを指します。

プレゼンテーションロジック:ユーザーがやり取りするもの、またはユーザーからのやり取りの結果であるもの。

これらの2つの懸念は、これらのタイプの通信に関連しています。

  • アプリケーションの状態
  • 親子
  • 子親
  • 兄弟姉妹

各タイプは適切な通信チャネルを使用する必要があります。


コミュニケーションチャンネル

チャンネルは、Vueアプリの周りでデータを交換するための具体的な実装を指すために使用するおおまかな用語です。

小道具(プレゼンテーションロジック)

直接親子通信用のVueの最も単純な通信チャネル。ほとんどの場合、プレゼンテーションロジックに関連するデータまたは制限されたデータセットを階層に渡すために使用する必要があります。

参照とメソッド(プレゼンテーションロジック)

子を親からのイベントを処理するために小道具を使用する意味がない場合、 子コンポーネントでrefを設定し、そのメソッドを呼び出す で十分です。

一部の人々は、これは親と子の間の密結合であると言うかもしれませんが、小道具を使用するのと同じ結合です。小道具の契約に同意できれば、メソッドの契約にも同意できます。

イベント(プレゼンテーションロジック)

$emitおよび$on。直接の親子通信のための最も簡単な通信チャネル。ここでも、プレゼンテーションロジックに使用する必要があります。

イベントバス(両方)

ほとんどの回答は、遠く離れたコンポーネント、または実際には何でも利用できる通信チャネルの1つであるイベントバスの優れた代替手段を提供します。

これは、他のコンポーネントがそれらを必要としない他のコンポーネントがほとんどない状態で、プロップを遠くから深くネストされた子コンポーネントに至る所で渡す場合に役立ちます。

注意:イベントバスに自身をバインドしているコンポーネントの後続の作成は、複数回バインドされます。複数のハンドラーがトリガーされ、リークが発生します。個人的に、過去にデザインしたすべてのシングルページアプリでイベントバスの必要性を感じたことはありませんでした。

次の例は、DOMから削除された場合でもItemコンポーネントがトリガーされる単純なミスがどのようにリークにつながるかを示しています。

// A component that binds to a custom 'update' event.
var Item = {
  template: `<li>{{text}}</li>`,
  props: {
    text: Number
  },
  mounted() {
    this.$root.$on('update', () => {
      console.log(this.text, 'is still alive');
    });
  },
};

// Component that emits events
var List = new Vue({
  el: '#app',
  components: {
    Item
  },
  data: {
    items: [1, 2, 3, 4]
  },
  updated() {
    this.$root.$emit('update');
  },
  methods: {
    onRemove() {
      console.log('slice');
      this.items = this.items.slice(0, -1);
    }
  }
});
<script src="https://unpkg.com/[email protected]/dist/vue.min.js"></script>

<div id="app">
  <button type="button" @click="onRemove">Remove</button>
  <ul>
    <item v-for="item in items" :key="item" :text="item"></item>
  </ul>
</div>

destroyedライフサイクルフックのリスナーを削除することを忘れないでください。

集中ストア(ビジネスロジック)

Vuex は、state managementのVueを使用する方法です。単なるイベント以上のものを提供し、本格的なアプリケーションに対応しています。

そして今 あなたが尋ねる

[S]マイナーなコミュニケーションごとにvuexのストアを作成できますか?

本当に輝いているのは:

  • ビジネスロジックを扱う、
  • バックエンドとの通信

そのため、コンポーネントは本来の意味に焦点を当て、ユーザーインターフェイスを管理できます。

コンポーネントロジックに使用できないという意味ではありませんが、必要なグローバルUI状態のみを持つ名前空間Vuexモジュールにそのロジックをスコープします。

グローバルな状態にあるすべての大きな混乱に対処するために、複数の名前空間モジュールにストアを分割する必要があります。


コンポーネントの種類

これらすべての通信を調整し、再利用性を容易にするために、コンポーネントを2つの異なるタイプと考える必要があります。

  • アプリ固有のコンテナー
  • 汎用コンポーネント

繰り返しになりますが、汎用コンポーネントを再利用する必要があることや、アプリ固有のコンテナを再利用できないことを意味するわけではありませんが、責任は異なります。

アプリ固有のコンテナー

これらは、他のVueコンポーネント(汎用または他のアプリ固有のコンテナー)をラップする単純なVueコンポーネントです。これは、Vuexストアの通信が発生する場所であり、このコンテナは、小道具やイベントリスナーなどの他の単純な手段を介して通信する必要があります。

これらのコンテナにはネイティブDOM要素がまったくなくてもよく、汎用コンポーネントでこれを処理できます。

scope何らかの理由でeventsまたはstores兄弟コンポーネントの可視性

これがスコーピングの発生場所です。ほとんどのコンポーネントはストアについて知らないため、このコンポーネントは(ほとんど)提供されたVuexマッパーで適用されるgettersおよびactionsの限定されたセットを持つ1つの名前空間ストアモジュールを使用する必要があります。

汎用コンポーネント

これらは、小道具からデータを受け取り、独自のローカルデータに変更を加え、簡単なイベントを発行する必要があります。ほとんどの場合、彼らはVuexストアの存在を知らないはずです。

他のUIコンポーネントにディスパッチすることが唯一の責任であるため、コンテナと呼ばれることもあります。


兄弟通信

それで、結局のところ、2つの兄弟コンポーネント間でどのように通信する必要がありますか?

例で理解する方が簡単です。入力ボックスがあり、そのデータをアプリ全体(ツリー内のさまざまな場所にある兄弟)で共有し、バックエンドで永続化する必要があるとします。

最悪のシナリオから始めて、コンポーネントはpresentationbusinessのロジックをミックスします。

// MyInput.vue
<template>
    <div class="my-input">
        <label>Data</label>
        <input type="text"
            :value="value" 
            :input="onChange($event.target.value)">
    </div>
</template>
<script>
    import axios from 'axios';

    export default {
        data() {
            return {
                value: "",
            };
        },
        mounted() {
            this.$root.$on('sync', data => {
                this.value = data.myServerValue;
            });
        },
        methods: {
            onChange(value) {
                this.value = value;
                axios.post('http://example.com/api/update', {
                        myServerValue: value
                    })
                    .then((response) => {
                        this.$root.$emit('update', response.data);
                    });
            }
        }
    }
</script>

これら2つの懸念を分離するには、コンポーネントをアプリ固有のコンテナーにラップし、プレゼンテーションロジックを汎用入力コンポーネントに保持する必要があります。

入力コンポーネントは再利用可能になり、バックエンドも兄弟も認識しません。

// MyInput.vue
// the template is the same as above
<script>
    export default {
        props: {
            initial: {
                type: String,
                default: ""
            }
        },
        data() {
            return {
                value: this.initial,
            };
        },
        methods: {
            onChange(value) {
                this.value = value;
                this.$emit('change', value);
            }
        }
    }
</script>

アプリ固有のコンテナは、ビジネスロジックとプレゼンテーションコミュニケーションの橋渡しとなります。

// MyAppCard.vue
<template>
    <div class="container">
        <card-body>
            <my-input :initial="serverValue" @change="updateState"></my-input>
            <my-input :initial="otherValue" @change="updateState"></my-input>

        </card-body>
        <card-footer>
            <my-button :disabled="!serverValue || !otherValue"
                       @click="saveState"></my-button>
        </card-footer>
    </div>
</template>
<script>
    import { mapGetters, mapActions } from 'vuex';
    import { NS, ACTIONS, GETTERS } from '@/store/modules/api';
    import { MyButton, MyInput } from './components';

    export default {
        components: {
            MyInput,
            MyButton,
        },
        computed: mapGetters(NS, [
            GETTERS.serverValue,
            GETTERS.otherValue,
        ]),
        methods: mapActions(NS, [
            ACTIONS.updateState,
            ACTIONS.updateState,
        ])
    }
</script>

Vuexストアactionsはバックエンド通信を処理するため、ここのコンテナはaxiosとバックエンドについて知る必要はありません。

35
Emile Bergeron

さて、v-onイベントを使用して、親を介して兄弟間で通信できます。

Parent
 |-List of items //sibling 1 - "List"
 |-Details of selected item //sibling 2 - "Details"

Detailsの要素をクリックしたときにListコンポーネントを更新したいとします。


Parentで:

テンプレート:

<list v-model="listModel"
      v-on:select-item="setSelectedItem" 
></list> 
<details v-model="selectedModel"></details>

ここに:

  • v-on:select-itemこれはイベントであり、Listコンポーネントで呼び出されます(以下を参照)。
  • setSelectedItemは、Parentを更新するselectedModelのメソッドです。

JS:

//...
data () {
  return {
    listModel: ['a', 'b']
    selectedModel: null
  }
},
methods: {
  setSelectedItem (item) {
    this.selectedModel = item //here we change the Detail's model
  },
}
//...

Listで:

テンプレート:

<ul>
  <li v-for="i in list" 
      :value="i"
      @click="select(i, $event)">
        <span v-text="i"></span>
  </li>
</ul>

JS:

//...
data () {
  return {
    selected: null
  }
},
props: {
  list: {
    type: Array,
    required: true
  }
},
methods: {
  select (item) {
    this.selected = item
    this.$emit('select-item', item) // here we call the event we waiting for in "Parent"
  },
}
//...

ここに:

  • this.$emit('select-item', item)は親のselect-itemを介して直接アイテムを送信します。そして、親はそれをDetailsビューに送信します
10
Sergei Panfilov

特に.syncが非推奨となったVueの通常の通信パターンを「ハッキング」したい場合に通常行うことは、コンポーネント間の通信を処理する簡単なEventEmitterを作成することです。私の最新プロジェクトの1つから:

import {EventEmitter} from 'events'

var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })

このTransmitterオブジェクトを使用すると、任意のコンポーネントで実行できます。

import Transmitter from './Transmitter'

var ComponentOne = Vue.extend({
  methods: {
    transmit: Transmitter.emit('update')
  }
})

「受信」コンポーネントを作成するには:

import Transmitter from './Transmitter'

var ComponentTwo = Vue.extend({
  ready: function () {
    Transmitter.on('update', this.doThingOnUpdate)
  }
})

繰り返しますが、これは本当に特定の用途向けです。アプリケーション全体をこのパターンに基づかず、代わりにVuexのようなものを使用してください。

5
Hector Lorenzo