web-dev-qa-db-fra.com

Communication entre composants frères dans VueJs 2.0

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 .

[ Lien vers la discussion ]

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 explicitement emit 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:

  • Chaque store et event a une visibilité globale (corrigez-moi si je me trompe);
  • C'est trop créer un nouveau magasin pour chaque communication mineure;

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?

83
Sergei Panfilov

Avec Vue 2.0, j'utilise le mécanisme eventHub comme indiqué dans documentation .

  1. 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
            }
        }
    })
    
  2. Maintenant, dans votre composant, vous pouvez émettre des événements avec

    this.eventHub.$emit('update', data)
    
  3. 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.

73
kakoni

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);
    });
}
91
Alex

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.


Types de communication

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:

  • État de l'application
  • Parent-enfant
  • Enfant parent
  • Frères et sœurs

Chaque type doit utiliser le bon canal de communication.


Canaux 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.

Accessoires (logique de présentation)

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.

Refs et méthodes (Logique de présentation)

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.

Événements (logique de présentation)

$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.

Bus événementiel (les deux)

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.

Magasin centralisé (logique métier)

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:

  • traiter avec votre logique métier,
  • communiquer avec un serveur

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.


Types de composants

Pour orchestrer toutes ces communications et 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 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 ou stores 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.

Composants génériques

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.


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

35
Emile Bergeron

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
10
Sergei Panfilov

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.

5
Hector Lorenzo