Cómo llamar a la función en el componente secundario en eventos principales

240

Contexto

En Vue 2.0, la documentación y otros indican claramente que la comunicación de padres a hijos ocurre a través de accesorios.

Pregunta

¿Cómo le dice un padre a su hijo que ha sucedido un evento a través de accesorios?

¿Debería ver un accesorio llamado evento? Eso no se siente bien, ni tampoco las alternativas ( $emit/ $ones de hijo a padre, y un modelo de centro es para elementos distantes).

Ejemplo

Tengo un contenedor principal y necesita decirle a su contenedor secundario que está bien realizar ciertas acciones en una API. Necesito poder activar funciones.

338

Dé el componente secundario ay refúselo $refspara llamar a un método en el componente secundario directamente.

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

Para obtener más información, consulte la documentación de Vue sobre refs .

9
  • 20
    De esta forma, los componentes padre e hijo se acoplan. Para eventos reales, digamos que cuando no puede simplemente cambiar un accesorio para desencadenar una acción, iría con la solución de bus sugerida por @Roy JJared 19/04/18 a las 17:12
  • 4
    una referencia a los documentos también sería útil vuejs.org/v2/guide/…ctf0 21/08/18 a las 1:22
  • 1
    Pregunta para principiantes: ¿Por qué usar en reflugar de crear un accesorio, que observe su valor y luego lo emita a otra función en el padre? Quiero decir que tiene muchas cosas que hacer, pero ¿es refseguro usarlo ? graciasIrfandy Jip 21/01/19 a las 7:18
  • 7
    @IrfandyJip - sí, refes seguro. En general, se desaconseja porque la comunidad de Vue prefiere pasar el estado a los niños y los eventos a los padres. En términos generales, esto conduce a componentes más aislados e internamente consistentes (algo bueno ™). Pero, si la información que le está pasando al niño es realmente un evento (o un comando), modificar el estado no es el patrón correcto. En ese caso, llamar a un método usando a refestá totalmente bien, y no se bloqueará ni nada. joerick 6 feb 2019 a las 9:58
  • 4
    @Jared todas las aplicaciones requieren algún nivel de acoplamiento. A menudo, un pequeño acoplamiento directo entre padre e hijo está bien si simplifica el código; la introducción de un bus de mensajes completo puede introducir una cantidad superflua de complejidad en el sistema cuando todo lo que se necesitaba era una única llamada de funciónjonny 19/06/20 a las 11:56
98

Lo que está describiendo es un cambio de estado en el padre. Se lo pasa al niño a través de un accesorio. Como sugirió, usaría watchese accesorio. Cuando el niño toma medidas, notifica al padre a través de un emit, y el padre puede volver a cambiar el estado.

var Child = {
  template: '<div>{{counter}}</div>',
  props: ['canI'],
  data: function () {
    return {
      counter: 0
    };
  },
  watch: {
    canI: function () {
      if (this.canI) {
        ++this.counter;
        this.$emit('increment');
      }
    }
  }
}
new Vue({
  el: '#app',
  components: {
    'my-component': Child
  },
  data: {
    childState: false
  },
  methods: {
    permitChild: function () {
      this.childState = true;
    },
    lockChild: function () {
      this.childState = false;
    }
  }
})
<script src="//cdnjs.cloudflare.com/ajax/libs/vue/2.2.1/vue.js"></script>
<div id="app">
<my-component :can-I="childState" v-on:increment="lockChild"></my-component>
<button @click="permitChild">Go</button>
</div>

Si realmente desea pasar eventos a un niño, puede hacerlo creando un bus (que es solo una instancia de Vue) y pasándolo al niño como un accesorio .

14
  • 4
    Creo que esta es la única respuesta en línea con la guía de estilo oficial y las mejores prácticas de Vue.JS. Si usa la abreviatura v-modelen el componente, también puede restablecer fácilmente el valor emitiendo el evento correspondiente con menos código. Falco 9/10/2017 a las 15:16
  • Por ejemplo, quiero dar una alerta cuando un usuario hace clic en un botón. ¿Propone, por ejemplo: - ver una bandera - establecer esta bandera de 0 a 1 cuando se produce un clic, - hacer algo - restablecer la banderaSinan Erdem 23/11/2017 a las 14:34
  • dieciséis
    Es muy incómodo, tienes que crear un extra propen un niño, una propiedad extra en data, luego agregar watch... Sería cómodo si hubiera soporte incorporado para transferir de alguna manera los eventos de padres a hijos. Esta situación ocurre con bastante frecuencia. Илья Зеленько 31 oct 2018 a las 16:02
  • 2
    Como afirma @ ИльяЗеленько, sucede con bastante frecuencia, sería una bendición en este momento. Stark 8/07/19 a las 20:28
  • 1
    Gracias @RoyJ, supongo que eso requiere que exista el accesorio del autobús cuando el niño se suscribe, sin embargo, supongo que la idea de enviar eventos a los niños no se recomienda en Vue. Ben Winding 29/06/20 a las 0:21
39

Puede utilizar $emity $on. Usando el código @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);
    }
  }
})

Ejemplo de ejecución: https://jsfiddle.net/rjurado/m2spy60r/1/

7
  • 7
    Me sorprende que funcione. Pensé que emitir a un niño era un anti-patrón, o que la intención era que emitir solo fuera de un niño a un padre. ¿Existe algún problema potencial al ir en sentido contrario? jbodily 6 mar 2017 a las 20:44
  • 2
    Puede que esto no se considere la mejor manera, no lo sé, pero si sabes lo que estás haciendo, creo que no hay problema. La otra forma es usar el bus central: vuejs.org/v2/guide/…drinor 7 de marzo de 2017 a las 7:24
  • 20
    Esto crea un acoplamiento entre el niño y el padre y se considera una mala prácticamorrislaptop 12 sep 2017 a las 16:14
  • 5
    Esto solo funciona porque el padre no es un componente, sino una aplicación vue. En realidad, esto está utilizando la instancia de vue como un bus. Julio Rodrigues 14 de septiembre de 2017 a las 14:57
  • 3
    @Bsienn la llamada a this. $ Parent hace que este componente sea dependiente del padre. usa $ emit to y props, por lo que las únicas dependencias son a través del sistema de comunicación de Vue. Este enfoque permite utilizar el mismo componente en cualquier lugar de la jerarquía de componentes. morrislaptop 27/07/19 a las 2:59
13

Una forma sencilla y desacoplada de llamar a métodos en componentes secundarios es emitir un controlador desde el elemento secundario y luego invocarlo desde el elemento principal.

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>

El padre realiza un seguimiento de las funciones del controlador secundario y llama cuando es necesario.

2
  • Me gusta a dónde va esta solución, pero ¿qué es exactamente "this.setter" en el padre? Craig 5/07/19 a las 15:16
  • 1
    Es la referencia de la función setValue emitida por el componente secundario como argumento para el evento del controlador. nilobarp 7/07/19 a las 16:16
9

No me gustó el enfoque de bus de eventos que usa $onenlaces en el niño durante create. ¿Por qué? Las createllamadas posteriores (que estoy usando vue-router) vinculan el controlador de mensajes más de una vez, lo que genera múltiples respuestas por mensaje.

La solución ortodoxa de pasar los apoyos de padres a hijos y poner un vigilante de la propiedad en el niño funcionó un poco mejor. El único problema es que el niño solo puede actuar en una transición de valores. Pasar el mismo mensaje varias veces necesita algún tipo de contabilidad para forzar una transición para que el niño pueda recoger el cambio.

Descubrí que si envuelvo el mensaje en una matriz, siempre activará el observador de niños, incluso si el valor sigue siendo el mismo.

Padre:

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

Niño:

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

HTML:

<parent>
   <child v-bind="{ 'msgChild': msgChild }"></child>
</parent>
4
  • 1
    Creo que esto no funcionará si msgChild tiene siempre el mismo estado en el padre. Por ejemplo: quiero un componente que abra un modal. Al padre no le importa si el estado actual es abierto o cerrado, solo quiere abrir el modal en cualquier momento. Entonces, si el padre hace esto, msgChild = true; el modal está cerrado, y luego el padre hace esto.msgChild = true, el niño no recibirá el eventoJorge Sainz 19/11/18 a las 10:34
  • 1
    @JorgeSainz: Es por eso que estoy envolviendo el valor en una matriz antes de asignarlo al elemento de datos. Sin envolver el valor en una matriz, se comporta tal como lo especifica. Entonces, msgChild = true, msgChild = true - sin evento. msgChild = [true], msgChild = [true] - evento! Jason Stewart 20/11/18 a las 11:20
  • 1
    Yo no lo vi. Gracias por la aclaraciónJorge Sainz 20/11/18 a las 14:01
  • Esto es genial, pero se siente un poco hack. Lo voy a usar ya que es más limpio que usar el componente ref hack y menos complicado que la solución de bus de eventos. Sé que vue quiere desacoplamiento y solo permite que los cambios de estado afecten al componente, pero debería haber alguna forma incorporada de llamar a los métodos de un niño si es necesario. Quizás un modificador en un accesorio que una vez que cambia de estado, podría restablecerlo automáticamente a un valor predeterminado para que el observador esté listo para el próximo cambio de estado. De todos modos, gracias por publicar su hallazgo. Craig 5/07/19 a las 15:51
5

Si tiene tiempo, use la tienda Vuex para ver variables (también conocido como estado) o desencadenar (también conocido como despacho) una acción directamente.

1
  • 2
    debido a la reactividad de vuejs / vuex, que es el mejor enfoque, en el padre realiza una acción / mutación que cambia el valor de una propiedad de vuex y en el hijo tiene un valor calculado que obtiene este mismo vuex $ store.state.property.value o un "reloj "método que hace algo cuando cambia vuex" $ store.state.property.value "FabianSilva 7 dic 2017 a las 15:21
5

El siguiente ejemplo es autoexplicativo. donde las referencias y los eventos se pueden usar para llamar a la función desde y hacia el padre y el hijo.

// 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>
1

Creo que deberíamos tener en cuenta la necesidad de que los padres utilicen los métodos del niño. De hecho, los padres no deben preocuparse por el método del niño, pero pueden tratar el componente secundario como una FSA (máquina de estados finitos). para controlar el estado del componente hijo, por lo que la solución para ver el cambio de estado o simplemente usar la función de cálculo es suficiente

1
  • 4
    Si está diciendo que "los padres nunca deben preocuparse por controlar a los niños", hay casos en los que esto es necesario. Considere un componente de temporizador de cuenta regresiva. Es posible que el padre desee restablecer el temporizador para comenzar de nuevo. El simple uso de accesorios no es suficiente porque pasar de tiempo = 60 a tiempo = 60 no modificará el accesorio. El temporizador debe exponer una función de 'reinicio' que el padre puede llamar según corresponda. tbm 15 abr.20 a las 16:53
1

Llamar al componente hijo en el padre

<component :is="my_component" ref="my_comp"></component>
<v-btn @click="$refs.my_comp.alertme"></v-btn>

en el componente hijo

mycomp.vue

    methods:{     
    alertme(){
            alert("alert")
            }
    }
0

puede usar la clave para recargar el componente secundario usando la clave

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

Si desea volver a cargar el componente con un nuevo filtro, si hace clic en el botón, filtrar el componente secundario

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

y usa el filtro para activar la función

0

Puede simular el envío de un evento a un hijo alternando un accesorio booleano en padre.

Código padre:

...
<child :event="event">
...
export default {
  data() {
    event: false
  },
  methods: {
    simulateEmitEventToChild() {
      this.event = !this.event;
    },
    handleExample() {
      this.simulateEmitEventToChild();
    }
  } 
}

Código de niño:

export default {
  props: {
    event: {
      type: Boolean
    }
  },
  watch: {
    event: function(value) {
      console.log("parent event");
    }
  }
}