In vuejs 2.0 model.sync
sera obsolète .
Alors, quel est le bon moyen de communiquer entre les composants frères dans vuejs 2. ?
Comme je saisis l'idée dans Vue 2.0 est d'avoir une communication fraternelle par en utilisant un magasin ou un bus d’événement .
Selon evan :
Il est également intéressant de mentionner que "passer des données entre composants" est généralement une mauvaise idée, car à la fin, le flux de données devient impossible à traquer et très difficile à déboguer.
Si un élément de données doit être partagé par plusieurs composants, préférez magasins globaux ou Vuex .
Et:
.once
et.sync
sont obsolètes. Les accessoires sont maintenant toujours à sens unique. Pour produire des effets secondaires dans l'étendue parente, un composant doit explicitementemit
un événement au lieu de s'appuyer sur une liaison implicite.
(Donc, il suggère est d'utiliser $emit
et $on
)
Je suis inquiet à cause de:
store
et event
a une visibilité globale (corrigez-moi si je me trompe);Ce que je veux, c'est viser en quelque sorte events
ou stores
visibilité pour les composants frères et sœurs. Ou peut-être que je n'ai pas saisi l'idée.
Alors, comment communiquer de la bonne manière?
Avec Vue 2.0, j'utilise le mécanisme eventHub comme indiqué dans documentation .
Définir un hub d'événements centralisé.
const eventHub = new Vue() // Single event hub
// Distribute to components using global mixin
Vue.mixin({
data: function () {
return {
eventHub: eventHub
}
}
})
Maintenant, dans votre composant, vous pouvez émettre des événements avec
this.eventHub.$emit('update', data)
Et pour t'écouter
this.eventHub.$on('update', data => {
// do your thing
})
Mise à jour Veuillez consulter la réponse de @ alex , qui décrit une solution plus simple.
Vous pouvez même le rendre plus court et utiliser l’instance rootVue
en tant que Global Event Hub:
Composant 1:
this.$root.$emit('eventing', data);
Composante 2:
mounted() {
this.$root.$on('eventing', data => {
console.log(data);
});
}
Je sais que c'est une vieille question, mais je voulais exposer d'autres canaux de communication et la façon de voir l'application et les communications d'un point de vue supérieur.
La première chose à comprendre lors de la conception d’une application Vue (ou de toute application à base de composants)) est qu’il existe différents types de communication qui dépendent des problèmes auxquels nous sommes confrontés et dont ils ont besoin. propres canaux de communication.
La 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:
Chaque type doit utiliser le bon canal de communication.
Un canal est un terme vague que je vais utiliser pour faire référence à des implémentations concrètes permettant d’échanger des données autour d’une application Vue app.
Le canal de communication le plus simple dans Vue pour une communication directe Parent-Enfant. Il devrait 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.
Lorsqu'il n'est pas judicieux 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 c'est très bien.
Certaines personnes diront peut-être qu’il s’agit d’un couplage étroit entre le parent et l’enfant, mais c’est le même couplage que celui qui consiste à utiliser des accessoires. Si nous pouvons nous mettre d’accord sur un contrat d’accessoires, nous pouvons aussi nous entendre sur un contrat de méthodes.
$emit
et $on
. Le canal de communication le plus simple pour la communication directe parent-enfant. Encore une fois, devrait être utilisé pour la logique de présentation.
La plupart des réponses offrent de bonnes alternatives au bus d’événements, qui est l’un des canaux de communication disponibles pour les composants distants, ou n’importe quoi.
Cela peut s'avérer utile lorsque vous passez des accessoires dans tous les sens, des composants enfants profondément imbriqués, sans qu'aucun autre composant n'en ait besoin entre les deux.
Faites attention: la création ultérieure de composants qui se lient au bus d'événements sera liée plusieurs fois - ce qui entraînera le déclenchement de plusieurs gestionnaires et la fuite. Personnellement, je n'ai jamais ressenti le besoin d'un bus d'événement dans toutes les applications d'une seule page que j'ai conçues par le passé.
Ce qui suit montre comment une simple erreur mène à une fuite dans laquelle le composant Item
se déclenche 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/[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>
N'oubliez pas de supprimer les écouteurs dans le hook destroyed
lifecycle.
Vuex est le chemin à parcourir avec Vue pour la gestion de l'état . Il offre beaucoup plus que des événements et il est prêt pour une application à grande échelle.
Et maintenant vous demandez :
Devrais-je créer le magasin de vuex pour chaque communication mineure?
Ça brille vraiment quand:
Ainsi, vos composants peuvent vraiment se concentrer sur ce qu’ils sont censés être, la gestion des interfaces utilisateur.
Cela ne signifie pas que vous ne pouvez pas l'utiliser pour la logique de composant, mais je voudrais étendre cette logique à un module Vuex à espace de noms avec uniquement l'état de l'interface utilisateur globale nécessaire.
Pour éviter de faire face à un fouillis de tout dans un état global, nous devons diviser le magasin en plusieurs modules namespaced.
Pour orchestrer toutes ces communications et faciliter la réutilisation, nous devons considérer les composants comme deux types différents.
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.
Ce sont des composants simples Vue qui englobe d’autres Vue (conteneurs génériques ou spécifiques à une application)). C’est là que la communication avec le magasin Vuex doit avoir lieu et ce conteneur devrait communiquer par d'autres moyens plus simples tels que les accessoires et les écouteurs d'événements.
Ces conteneurs pourraient même ne comporter aucun élément DOM natif et laisser les composants génériques s'en charger.
scope en quelque sorte
events
oustores
visibilité pour les composants siblings
C'est là que se produit la portée. La plupart des composants ne connaissent pas le magasin et ce composant doit (principalement) utiliser un module de magasin à espace de noms avec un ensemble limité de getters
et actions
appliqués avec les mappeurs Vuex fournis.
Ceux-ci doivent 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 qu’un magasin Vuex existe.
Ils pourraient également être appelés conteneurs car leur seule responsabilité pourrait être d'expédier vers d'autres composants de l'interface utilisateur.
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 conservées avec un backend.
En commençant par le pire scénario , notre composant mélangerait présentation et entreprise logique.
// 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 à une application et conserver la logique de présentation dans notre composant d'entrée générique.
Notre composant d’entrée est maintenant 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 à une application peut maintenant être le 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>
Depuis que le magasin Vuex actions gère la communication backend, notre conteneur n’a pas besoin de connaître axios ni le backend.
D'accord, nous pouvons communiquer entre frères et soeurs via parent en utilisant les événements v-on
.
Parent
|-List of items //sibling 1 - "List"
|-Details of selected item //sibling 2 - "Details"
Supposons que nous souhaitons mettre à jour le composant Details
lorsque nous cliquons sur un élément de List
.
dans Parent
:
Modèle:
<list v-model="listModel"
v-on:select-item="setSelectedItem"
></list>
<details v-model="selectedModel"></details>
Ici:
v-on:select-item
C'est un événement qui sera appelé dans le composant List
(voir ci-dessous);setSelectedItem
c'est une méthode de Parent
pour mettre à jour selectedModel
;JS:
//...
data () {
return {
listModel: ['a', 'b']
selectedModel: null
}
},
methods: {
setSelectedItem (item) {
this.selectedModel = item //here we change the Detail's model
},
}
//...
Dans List
:
Modèle:
<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"
},
}
//...
Ici:
this.$emit('select-item', item)
enverra l'élément via select-item
directement au parent. Et le parent l'enverra à la vue Details
Ce que je fais habituellement si je veux "pirater" les schémas de communication normaux dans Vue, spécialement maintenant que .sync
est obsolète, consiste à créer un simple EventEmitter qui gère la communication entre les composants. De l'un de mes derniers projets:
import {EventEmitter} from 'events'
var Transmitter = Object.assign({}, EventEmitter.prototype, { /* ... */ })
Avec cet objet Transmitter
, vous pouvez ensuite, dans n’importe quel composant:
import Transmitter from './Transmitter'
var ComponentOne = Vue.extend({
methods: {
transmit: Transmitter.emit('update')
}
})
Et pour créer un composant "récepteur":
import Transmitter from './Transmitter'
var ComponentTwo = Vue.extend({
ready: function () {
Transmitter.on('update', this.doThingOnUpdate)
}
})
Encore une fois, c'est pour des utilisations vraiment spécifiques. Ne basez pas toute votre application sur ce modèle, utilisez plutôt quelque chose comme Vuex
.