通信タイプ
Vueアプリケーション(または実際にはコンポーネントベースのアプリケーション)を設計する場合、さまざまな通信タイプがあり、それらは私たちが扱っている懸念に依存し、それらには独自の通信チャネルがあります。
ビジネスロジック:アプリとその目標に固有のすべてを指します。
プレゼンテーションロジック:ユーザーが操作するもの、またはユーザーからの操作に起因するもの。
これらの2つの懸念は、次のタイプの通信に関連しています。
各タイプは適切な通信チャネルを使用する必要があります。
コミュニケーションチャンネル
チャネルとは、Vueアプリの周囲でデータを交換するための具体的な実装を指すために使用する緩い用語です。
小道具:親子プレゼンテーションロジック
直接の親子通信のためのVueで最も単純な通信チャネル。これは主に、プレゼンテーションロジックに関連するデータや、階層を下って制限されたデータセットを渡すために使用する必要があります。
参照と方法:プレゼンテーションのアンチパターン
プロップを使用して子が親からのイベントを処理できるようにしても意味がない場合は、子コンポーネントにを設定してref
そのメソッドを呼び出すだけで問題ありません。
それをしないでください、それはアンチパターンです。コンポーネントアーキテクチャとデータフローを再考します。親から子コンポーネントのメソッドを呼び出したい場合は、状態を上げるか、ここまたは他の回答で説明されている他の方法を検討するときです。
イベント:子親プレゼンテーションロジック
$emit
と$on
。直接の親子通信のための最も単純な通信チャネル。この場合も、プレゼンテーションロジックに使用する必要があります。
イベントバス
ほとんどの回答は、遠方のコンポーネントや実際には何でも使用できる通信チャネルの1つであるイベントバスの優れた代替案を示しています。
これは、他のコンポーネントがこれらの間にほとんど必要とせずに、小道具を遠くから下に深くネストされた子コンポーネントに渡すときに役立ちます。慎重に選択したデータには慎重に使用してください。
注意:イベントバスに自分自身をバインドしているコンポーネントのその後の作成は、複数回バインドされ、複数のハンドラーがトリガーされてリークします。個人的に、過去にデザインしたすべてのシングルページアプリでイベントバスの必要性を感じたことはありません。
以下は、単純なミスがItem
、DOMから削除された場合でもコンポーネントが引き続きトリガーされるリークにつながる方法を示しています。
// 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/vue@2.5.17/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は、Vueを使用して状態を管理する方法です。イベントだけでなく、本格的なアプリケーションの準備ができています。
そして今あなたは尋ねます:
[S]マイナーコミュニケーションごとにvuexのストアを作成する必要がありますか?
それは本当に輝きます:
- あなたのビジネスロジックを扱う、
- バックエンド(またはローカルストレージなどのデータ永続化レイヤー)との通信
そのため、コンポーネントは、ユーザーインターフェースの管理など、本来あるべきことに集中できます。
コンポーネントロジックに使用できないという意味ではありませんが、必要なグローバルUI状態のみを持つ名前空間付きのVuexモジュールにそのロジックのスコープを設定します。
グローバル状態のすべての大きな混乱を処理することを回避するには、ストアを複数の名前空間モジュールに分離する必要があります。
コンポーネントタイプ
これらすべての通信を調整し、再利用を容易にするために、コンポーネントを2つの異なるタイプと考える必要があります。
繰り返しになりますが、これは汎用コンポーネントを再利用する必要があることや、アプリ固有のコンテナを再利用できないことを意味するものではありませんが、責任は異なります。
アプリ固有のコンテナ
これらは、他のVueコンポーネント(汎用または他のアプリ固有のコンテナー)をラップする単純なVueコンポーネントです。これは、Vuexストア通信が発生する場所であり、このコンテナーは、小道具やイベントリスナーなどの他のより単純な手段を通じて通信する必要があります。
これらのコンテナーは、ネイティブDOM要素をまったく持たず、汎用コンポーネントがテンプレートとユーザーの対話を処理できるようにすることもできます。
スコープ何とかevents
またはstores
兄弟コンポーネントの可視性
スコーピングはここで行われます。ほとんどのコンポーネントはストアについて認識していません。このコンポーネントは、(ほとんどの場合)限定されたセットの1つの名前空間付きストアモジュールを使用し、提供されたVuexバインディングヘルパーgetters
をactions
適用する必要があります。
一般的なコンポーネント
これらは、小道具からデータを受け取り、独自のローカルデータに変更を加え、単純なイベントを発行する必要があります。ほとんどの場合、Vuexストアが存在することをまったく知らないはずです。
他のUIコンポーネントへのディスパッチが唯一の責任である可能性があるため、これらはコンテナーと呼ばれることもあります。
兄弟のコミュニケーション
それでは、この後、2つの兄弟コンポーネント間でどのように通信する必要がありますか?
例を使用すると理解しやすくなります。入力ボックスがあり、そのデータはアプリ全体で共有され(ツリー内の異なる場所にある兄弟)、バックエンドで永続化されている必要があるとします。
最悪のシナリオから始めて、私たちのコンポーネントはプレゼンテーションとビジネスロジックを混合します。
// 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ストアアクションはバックエンド通信を処理するため、ここでのコンテナーはaxios とバックエンドについて知る必要はありません。
$emit
と組み合わせv-model
てエミュレートし.sync
ます。Vuexの方法を使うべきだと思います