Comment appeler une fonction sur un composant enfant sur des événements parents

164

Le contexte

Dans Vue 2.0, la documentation et d' autres indiquent clairement que la communication du parent à l'enfant se fait via des accessoires.

Question

Comment un parent informe-t-il son enfant qu'un événement s'est produit via des accessoires?

Dois-je simplement regarder un accessoire appelé événement? Cela ne semble pas juste, pas plus que les alternatives ( $emit/ $onest pour l'enfant à parent, et un modèle de hub est pour les éléments distants).

Exemple

J'ai un conteneur parent et il doit indiquer à son conteneur enfant qu'il est normal d'engager certaines actions sur une API. J'ai besoin de pouvoir déclencher des fonctions.

jbodily
la source

Réponses:

234

Donnez au composant enfant a refet utilisez $refspour appeler une méthode directement sur le composant enfant.

html:

<div id="app">
  <child-component ref="childComponent"></child-component>
  <button @click="click">Click</button>  
</div>

javascript:

var ChildComponent = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
    setValue: function(value) {
        this.value = value;
    }
  }
}

new Vue({
  el: '#app',
  components: {
    'child-component': ChildComponent
  },
  methods: {
    click: function() {
        this.$refs.childComponent.setValue(2.0);
    }
  }
})

Pour plus d'informations, consultez la documentation Vue sur les refs .

Joerick
la source
13
De cette façon, les composants parents et enfants sont couplés. Pour les événements réels, disons que lorsque vous ne pouvez pas simplement changer un accessoire pour déclencher une action, j'irais avec la solution de bus suggérée par @Roy J
Jared
3
une référence à la documentation serait utile aussi vuejs.org/v2/guide/…
ctf0
Dans mon composant enfant, pour des raisons particulières, j'ai dû utiliser v-once pour terminer la réactivité. Ainsi, passer l'accessoire de parent à enfant n'était pas une option, donc cette solution a fait l'affaire!
John
1
question du débutant: Pourquoi utiliser refau lieu de créer un accessoire, qui surveille sa valeur puis l'émet vers une autre fonction dans le parent? Je veux dire qu'il a beaucoup de choses à faire, mais est-ce que l'utilisation est refencore sûre? Merci
Irfandy Jip
5
@IrfandyJip - oui, refc'est sûr. Généralement, c'est déconseillé car la communauté Vue préfère transmettre l'état aux enfants et les événements au parent. De manière générale, cela conduit à des composants plus isolés et cohérents en interne (une bonne chose ™). Mais, si les informations que vous transmettez à l'enfant sont vraiment un événement (ou une commande), modifier l'état n'est pas le bon modèle. Dans ce cas, appeler une méthode en utilisant a refest tout à fait correct, et cela ne plantera pas ou quoi que ce soit.
joerick
76

Ce que vous décrivez est un changement d'état chez le parent. Vous transmettez cela à l'enfant via un accessoire. Comme vous l'avez suggéré, vous auriez watchcet accessoire. Lorsque l'enfant prend des mesures, il en informe le parent via un emit, et le parent peut alors modifier à nouveau l'état.

Si vous voulez vraiment passer des événements à un enfant, vous pouvez le faire en créant un bus (qui n'est qu'une instance de Vue) et en le passant à l'enfant en tant qu'accessoire .

Roy J
la source
2
Je pense que c'est la seule réponse en ligne avec le guide de style officiel de Vue.JS et les meilleures pratiques. Si vous utilisez le raccourci v-modelsur le composant, vous pouvez également réinitialiser facilement la valeur en émettant l'événement correspondant avec moins de code.
Falco
Par exemple, je souhaite émettre une alerte lorsqu'un utilisateur clique sur un bouton. Proposez-vous par exemple: - regarder un drapeau - mettre ce drapeau de 0 à 1 lorsqu'un clic se produit, - faire quelque chose - réinitialiser le drapeau
Sinan Erdem
9
C'est très inconfortable, vous devez créer un extra propchez un enfant, une propriété supplémentaire dans data, puis ajouter watch... Ce serait confortable s'il y avait un support intégré pour transférer en quelque sorte les événements du parent à l'enfant. Cette situation se produit assez souvent.
Илья Зеленько
1
Comme l'indique @ ИльяЗеленько, cela arrive assez souvent, ce serait une aubaine en ce moment.
Craig
1
Merci @RoyJ, je suppose que cela nécessite que l'accessoire de bus existe lorsque l'enfant y souscrit, je suppose que l'idée même d'envoyer des événements aux enfants est découragée dans Vue.
Ben Winding le
35

Vous pouvez utiliser $emitet $on. Utilisation du code @RoyJ:

html:

<div id="app">
  <my-component></my-component>
  <button @click="click">Click</button>  
</div>

javascript:

var Child = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
    setValue: function(value) {
        this.value = value;
    }
  },
  created: function() {
    this.$parent.$on('update', this.setValue);
  }
}

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  methods: {
    click: function() {
        this.$emit('update', 7);
    }
  }
})

Exemple en cours d'exécution: https://jsfiddle.net/rjurado/m2spy60r/1/

drinor
la source
6
Je suis surpris que cela fonctionne. Je pensais qu'émettre à un enfant était un anti-modèle, ou que l'intention était d'émettre uniquement d'un enfant à un parent. Y a-t-il des problèmes potentiels à aller dans l'autre sens?
jbodily
2
Cela peut ne pas être considéré comme le meilleur moyen, je ne sais pas, mais si vous savez ce que vous faites, je pense que ce n'est pas un problème. L'autre moyen est d'utiliser le bus central: vuejs.org/v2/guide/…
drinor
14
Cela crée un couplage entre l'enfant et le parent et est considéré comme une mauvaise pratique
morrislaptop
5
Cela ne fonctionne que parce que le parent n'est pas un composant mais en fait une application vue. En réalité, cela utilise l'instance vue comme un bus.
Julio Rodrigues le
2
@Bsienn l'appel à this. $ Parent rend ce composant dépendant du parent. utilise $ emit to et props pour que les seules dépendances soient via le système de communication de Vue. Cette approche permet au même composant d'être utilisé n'importe où dans la hiérarchie des composants.
morrislaptop
6

Si vous avez le temps, utilisez Vuex store pour regarder les variables (aka state) ou déclencher (aka dispatch) une action directement.

brightknight08
la source
2
en raison de la réactivité de vuejs / vuex qui est la meilleure approche, dans parent faire une action / mutation qui modifie une valeur de propriété vuex et dans child avoir une valeur calculée qui obtient cette même vuex $ store.state.property.value ou une "watch "méthode qui fait quelque chose quand vuex" $ store.state.property.value "change
FabianSilva
6

N'a pas aimé l' approche du bus d'événements utilisant des $onliaisons chez l'enfant pendant create. Pourquoi? createAppels ultérieurs (j'utilisevue-router ) lient le gestionnaire de messages plus d'une fois, ce qui entraîne plusieurs réponses par message.

La solution orthodoxe consistant à faire passer des accessoires de parent à enfant et à mettre un surveillant de propriété chez l'enfant fonctionnait un peu mieux. Seul hic étant que l'enfant ne peut agir que sur une transition de valeur. Passer le même message plusieurs fois nécessite une sorte de comptabilité pour forcer une transition afin que l'enfant puisse récupérer la monnaie.

J'ai constaté que si j'emballe le message dans un tableau, cela déclenchera toujours l'observateur enfant - même si la valeur reste la même.

Parent:

{
   data: function() {
      msgChild: null,
   },
   methods: {
      mMessageDoIt: function() {
         this.msgChild = ['doIt'];
      }
   }   
   ...
}

Enfant:

{
   props: ['msgChild'],
   watch: {
      'msgChild': function(arMsg) {
         console.log(arMsg[0]);
      }
   }
}

HTML:

<parent>
   <child v-bind="{ 'msgChild': msgChild }"></child>
</parent>
Jason Stewart
la source
1
Je pense que cela ne fonctionnera pas si msgChild a toujours le même statut sur le parent. Par exemple: je veux un composant qui ouvre un modal. Le parent ne se soucie pas de savoir si l'état actuel est ouvert ou fermé, il veut simplement ouvrir le modal à tout moment. Donc, si le parent le fait, msgChild = true; le modal est fermé, puis le parent le fait.msgChild = true, l'enfant ne recevra pas l'événement
Jorge Sainz
1
@JorgeSainz: C'est pourquoi j'emballe la valeur dans un tableau avant de l'attribuer à l'élément de données. Sans encapsuler la valeur dans un tableau, il se comporte comme vous le spécifiez. Donc, msgChild = true, msgChild = true - pas d'événement. msgChild = [true], msgChild = [true] - événement!
Jason Stewart
1
Je ne l'ai pas vu. Merci pour la clarification
Jorge Sainz
C'est cool, mais c'est un peu hackish. Je vais l'utiliser car c'est plus propre que d'utiliser le composant ref hack et moins compliqué que la solution de bus d'événements. Je sais que vue veut un découplage et n'autorise que les changements d'état à affecter le composant, mais il devrait y avoir une manière intégrée d'appeler les méthodes d'un enfant si nécessaire. Peut-être un modificateur sur un accessoire qui, une fois qu'il change d'état, vous pouvez le réinitialiser automatiquement à une valeur par défaut afin que l'observateur soit prêt pour le prochain changement d'état. Quoi qu'il en soit, merci d'avoir publié votre trouvaille.
Craig
6

Un moyen découplé simple d'appeler des méthodes sur des composants enfants consiste à émettre un gestionnaire depuis l'enfant, puis à l'appeler depuis le parent.

var Child = {
  template: '<div>{{value}}</div>',
  data: function () {
    return {
      value: 0
    };
  },
  methods: {
  	setValue(value) {
    	this.value = value;
    }
  },
  created() {
    this.$emit('handler', this.setValue);
  }
}

new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  methods: {
  	setValueHandler(fn) {
    	this.setter = fn
    },
    click() {
    	this.setter(70)
    }
  }
})
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>

<div id="app">
  <my-component @handler="setValueHandler"></my-component>
  <button @click="click">Click</button>  
</div>

Le parent effectue le suivi des fonctions du gestionnaire enfant et des appels chaque fois que nécessaire.

nilobarpe
la source
J'aime où va cette solution, mais qu'est-ce que "this.setter" exactement dans le parent?
Craig
Il s'agit de la référence de fonction setValue émise par le composant enfant en tant qu'argument de l'événement du gestionnaire.
nilobarp
2

L'exemple ci-dessous est auto-explicatif. où les références et les événements peuvent être utilisés pour appeler la fonction depuis et vers le parent et l'enfant.

// PARENT
<template>
  <parent>
    <child
      @onChange="childCallBack"
      ref="childRef"
      :data="moduleData"
    />
    <button @click="callChild">Call Method in child</button>
  </parent>
</template>

<script>
export default {
  methods: {
    callChild() {
      this.$refs.childRef.childMethod('Hi from parent');
    },
    childCallBack(message) {
      console.log('message from child', message);
    }
  }
};
</script>

// CHILD
<template>
  <child>
    <button @click="callParent">Call Parent</button>
  </child>
</template>

<script>
export default {
  methods: {
    callParent() {
      this.$emit('onChange', 'hi from child');
    },
    childMethod(message) {
      console.log('message from parent', message);
    }
  }
}
</script>
Mukundhan
la source
1

Je pense que nous devrions tenir compte de la nécessité pour le parent d'utiliser les méthodes de l'enfant.En fait, les parents n'ont pas à se soucier de la méthode de l'enfant, mais peuvent traiter le composant enfant comme un FSA (machine à états finis). pour contrôler l'état du composant enfant.Ainsi, la solution pour observer le changement d'état ou simplement utiliser la fonction de calcul suffit

user10097040
la source
2
Si vous dites que «les parents ne devraient jamais se préoccuper de contrôler les enfants», il y a des cas où cela est nécessaire. Considérez un composant de compte à rebours. Le parent peut souhaiter réinitialiser la minuterie pour recommencer. Utiliser simplement des accessoires ne suffit pas car passer de time = 60 à time = 60 ne modifiera pas l'accessoire. Le minuteur doit exposer une fonction de «réinitialisation» que le parent peut appeler le cas échéant.
tbm le
0

vous pouvez utiliser la clé pour recharger le composant enfant en utilisant la clé

<component :is="child1" :filter="filter" :key="componentKey"></component>

Si vous souhaitez recharger le composant avec un nouveau filtre, si vous cliquez sur le bouton, filtrez le composant enfant

reloadData() {            
   this.filter = ['filter1','filter2']
   this.componentKey += 1;  
},

et utilisez le filtre pour déclencher la fonction

Ardian Zack
la source