Types de communication
Lors de la conception d'une application Vue (ou en fait de toute application basée sur des composants), il existe différents types de communication qui dépendent des préoccupations auxquelles nous sommes confrontés et ils ont leurs propres canaux de communication.
Logique métier: fait référence à tout ce qui est spécifique à votre application et à son objectif.
Logique de présentation: tout ce avec quoi l'utilisateur interagit ou qui résulte de l'interaction de l'utilisateur.
Ces deux préoccupations sont liées à ces types de communication:
- État de l'application
- Parent-enfant
- Enfant-parent
- Fratrie
Chaque type doit utiliser le bon canal de communication.
Canaux de communication
Un canal est un terme vague que j'utiliserai pour faire référence à des implémentations concrètes pour échanger des données autour d'une application Vue.
Accessoires: logique de présentation parent-enfant
Le canal de communication le plus simple de Vue pour une communication directe parent-enfant . Il doit principalement être utilisé pour transmettre des données relatives à la logique de présentation ou à un ensemble restreint de données dans la hiérarchie.
Réfs et méthodes: Présentation anti-motif
Quand cela n'a pas de sens d'utiliser un accessoire pour laisser un enfant gérer un événement d'un parent, configurer un ref
sur le composant enfant et appeler ses méthodes est très bien.
Ne faites pas ça, c'est un anti-modèle. Repensez l'architecture de vos composants et le flux de données. Si vous souhaitez appeler une méthode sur un composant enfant d'un parent, il est probablement temps de lever l'état ou d'envisager les autres méthodes décrites ici ou dans les autres réponses.
Evénements: logique de présentation enfant-parent
$emit
et $on
. Le canal de communication le plus simple pour une communication directe enfant-parent. Encore une fois, devrait être utilisé pour la logique de présentation.
Bus événementiel
La plupart des réponses offrent de bonnes alternatives pour le bus d'événements, qui est l'un des canaux de communication disponibles pour les composants distants, ou quoi que ce soit en fait.
Cela peut devenir utile lorsque vous passez des accessoires partout, de loin vers le bas, à des composants enfants profondément imbriqués, presque aucun autre composant n'en ayant besoin entre les deux. Utilisez avec parcimonie pour des données soigneusement sélectionnées.
Attention: la création ultérieure de composants qui se lient au bus d'événements sera liée plus d'une fois, ce qui entraînera le déclenchement de plusieurs gestionnaires et des fuites. Personnellement, je n'ai jamais ressenti le besoin d'un bus événementiel dans toutes les applications à page unique que j'ai conçues dans le passé.
Ce qui suit montre comment une simple erreur conduit à une fuite où le Item
composant se déclenche toujours même s'il est supprimé du 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>
N'oubliez pas de supprimer les auditeurs dans le destroyed
hook de cycle de vie.
Magasin centralisé (logique métier)
Vuex est la voie à suivre avec Vue pour la gestion de l'état . Il offre bien plus que de simples événements et il est prêt pour une application à grande échelle.
Et maintenant vous demandez :
[P] Dois-je créer le magasin de vuex pour chaque communication mineure?
Ça brille vraiment quand:
- gérer votre logique métier,
- communiquer avec un backend (ou toute couche de persistance des données, comme le stockage local)
Ainsi, vos composants peuvent vraiment se concentrer sur ce qu'ils sont censés être, en gérant les interfaces utilisateur.
Cela ne signifie pas que vous ne pouvez pas l'utiliser pour la logique des composants, mais j'appliquerais cette logique à un module Vuex à espace de noms avec uniquement l'état d'interface utilisateur global nécessaire.
Pour éviter de gérer un gros désordre de tout dans un état global, le magasin doit être séparé en plusieurs modules d'espacement de noms.
Types de composants
Pour orchestrer toutes ces communications et pour faciliter la réutilisation, nous devons considérer les composants comme deux types différents.
- Conteneurs spécifiques à l'application
- Composants génériques
Encore une fois, cela ne signifie pas qu'un composant générique doit être réutilisé ou qu'un conteneur spécifique à une application ne peut pas être réutilisé, mais ils ont des responsabilités différentes.
Conteneurs spécifiques à l'application
Ce ne sont que de simples composants Vue qui enveloppent d'autres composants Vue (conteneurs génériques ou spécifiques à d'autres applications). C'est là que la communication du magasin Vuex doit avoir lieu et ce conteneur doit communiquer par d'autres moyens plus simples comme les accessoires et les écouteurs d'événements.
Ces conteneurs peuvent même ne contenir aucun élément DOM natif et laisser les composants génériques gérer la création de modèles et les interactions des utilisateurs.
portée en quelque sorte events
ou stores
visibilité pour les composants frères et sœurs
C'est là que se déroule le cadrage. La plupart des composants ne connaissent pas le magasin et ce composant devrait (principalement) utiliser un module de magasin avec un espace de noms limité getters
et actions
appliqué avec le assistants de liaison Vuex .
Composants génériques
Ceux-ci devraient recevoir leurs données des accessoires, apporter des modifications à leurs propres données locales et émettre des événements simples. La plupart du temps, ils ne devraient pas savoir du tout qu'un magasin Vuex existe.
Ils pourraient également être appelés conteneurs, car leur seule responsabilité pourrait être de les envoyer à d'autres composants de l'interface utilisateur.
Communication fraternelle
Alors, après tout cela, comment devrions-nous communiquer entre deux composants frères?
C'est plus facile à comprendre avec un exemple: disons que nous avons une zone de saisie et que ses données doivent être partagées dans l'application (frères et sœurs à différents endroits de l'arborescence) et persistantes avec un backend.
En commençant par le pire des cas , notre composant mélangerait présentation et logique métier .
// 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>
Pour séparer ces deux préoccupations, nous devons envelopper notre composant dans un conteneur spécifique à l'application et conserver la logique de présentation dans notre composant d'entrée générique.
Notre composant d'entrée est désormais réutilisable et ne connaît ni le backend ni les frères et sœurs.
// 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>
Notre conteneur spécifique à l'application peut désormais servir de pont entre la logique métier et la communication de présentation.
// 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>
Étant donné que les actions du magasin Vuex concernent la communication du backend, notre conteneur n'a pas besoin de connaître axios et le backend.
$emit
combiné avecv-model
pour émuler.sync
. Je pense que vous devriez suivre la voie