Cómo acceder a un método secundario desde el padre en vue.js

2 minutos de lectura

avatar de usuario
al3x

Tengo dos componentes anidados, ¿cuál es la forma correcta de acceder a los métodos secundarios desde el principal?

this.$children[0].myMethod() parece hacer el truco, pero es bastante feo, ¿no es así? ¿Qué puede ser mejor?

<script>
import child from './my-child'

export default {
  components: {
   child
  },
  mounted () {
    this.$children[0].myMethod()
  }
}
</script>

  • Primero, pregúntese si realmente necesita hacerlo. Si todo el estado de su página está en una tienda, como debería ser, no hay necesidad de comunicación entre padres e hijos.

    – bbsimonbb

    30 de noviembre de 2017 a las 8:25

  • @bbsimonbb El estado es diferente de los eventos. Esto se trata específicamente de desencadenar eventos secundarios desde los padres. También puede hacer lo que sea para lo que estaría usando Vuex pasando un accesorio en sentido descendente, pero esto requiere que el componente secundario observe los cambios en el accesorio/tienda para que pueda emular efectivamente RPC con cambios de datos, lo cual es simplemente incorrecto cuando todo lo que quiere es activar una acción en el componente.

    – Bojan Markovic

    13 de julio de 2018 a las 13:52

avatar de usuario
Desmond Lua

Puedes usar árbitro.

import ChildForm from './components/ChildForm'

new Vue({
  el: '#app',
  data: {
    item: {}
  },
  template: `
  <div>
     <ChildForm :item="item" ref="form" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.$refs.form.submit()
    }
  },
  components: { ChildForm },
})

Si no le gusta el acoplamiento apretado, puede usar Autobús de eventos como lo muestra @Yosvel Quintero. A continuación se muestra otro ejemplo del uso del bus de eventos al pasar el bus como accesorios.

import ChildForm from './components/ChildForm'

new Vue({
  el: '#app',
  data: {
    item: {},
    bus: new Vue(),
  },
  template: `
  <div>
     <ChildForm :item="item" :bus="bus" ref="form" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.bus.$emit('submit')
    }
  },
  components: { ChildForm },
})

Código de componente.

<template>
 ...
</template>

<script>
export default {
  name: 'NowForm',
  props: ['item', 'bus'],
  methods: {
    submit() {
        ...
    }
  },
  mounted() {
    this.bus.$on('submit', this.submit)
  },  
}
</script>

https://code.luasoftware.com/tutorials/vuejs/parent-call-child-component-method/

  • Esta es la respuesta correcta, que en realidad lee la pregunta real. La respuesta seleccionada en realidad responde a la pregunta opuesta (cómo activar un método en el componente principal desde el componente secundario).

    – Bojan Markovic

    13 de julio de 2018 a las 13:42

  • el enlace para Autobús de eventos que esta respuesta está enlazando, está redirigiendo a Administración del Estadodespués de leer el comentario de @bbsimonbb, tiene sentido.

    – Eido95

    9 de enero de 2019 a las 17:16

  • Vale la pena mencionar si usas this.$refs.no debe cargar el componente secundario dinámicamente.

    – 1_error

    18 de noviembre de 2019 a las 10:56

  • ¡gracias Señor! Me ahorraste muchos problemas. ¡Estaba solucionando un problema de producción y estaba buscando respuestas desesperadamente!

    –Osama Ibrahim

    5 jun 2020 a las 21:30

  • this.$ref.ref parece devolver una matriz. entonces para mi this.$refs.ref[0].autofocus(); trabajó

    – Jozef Plata

    17 de noviembre de 2020 a las 0:13


avatar de usuario
Yosvel Quintero Argüelles

Comunicación padre-hijo en VueJS

Dada una instancia raíz de Vue, todos los descendientes pueden acceder a través de this.$rootun componente principal puede acceder a los componentes secundarios a través del this.$children matriz, y un componente hijo puede acceder a su padre a través de this.$parentsu primer instinto podría ser acceder a estos componentes directamente.

La documentación de VueJS advierte contra esto específicamente por dos muy buenas razones:

  • Acopla estrechamente al padre con el hijo (y viceversa)
  • No puede confiar en el estado del padre, dado que puede ser modificado por un componente hijo.

La solución es usar la interfaz de eventos personalizados de Vue

La interfaz de eventos implementada por Vue le permite comunicarse hacia arriba y hacia abajo en el árbol de componentes. Aprovechar la interfaz de eventos personalizados le da acceso a cuatro métodos:

  1. $on() – le permite declarar un oyente en su instancia de Vue con el que escuchar eventos
  2. $emit() – le permite activar eventos en la misma instancia (auto)

Ejemplo usando $on() y $emit():

const events = new Vue({}),
    parentComponent = new Vue({
      el: '#parent',
      ready() {
        events.$on('eventGreet', () => {
          this.parentMsg = `I heard the greeting event from Child component ${++this.counter} times..`;
        });
      },
      data: {
        parentMsg: 'I am listening for an event..',
        counter: 0
      }
    }),
    childComponent = new Vue({
      el: '#child',
      methods: {
      greet: function () {
        events.$emit('eventGreet');
        this.childMsg = `I am firing greeting event ${++this.counter} times..`;
      }
    },
    data: {
      childMsg: 'I am getting ready to fire an event.',
      counter: 0
    }
  });
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/1.0.28/vue.min.js"></script>

<div id="parent">
  <h2>Parent Component</h2>
  <p>{{parentMsg}}</p>
</div>

<div id="child">
  <h2>Child Component</h2>
  <p>{{childMsg}}</p>
  <button v-on:click="greet">Greet</button>
</div>

Respuesta tomada de la publicación original: Comunicación entre componentes en VueJS

  • ¡Gracias, intentaré mutualizar mi código a través de eventos!

    – al3x

    4 de diciembre de 2016 a las 10:06

  • al copiar/pegar cosas, es bueno mencionar también la fuente.

    – Mihai Vilcu

    4 de diciembre de 2016 a las 17:04

  • Esto es útil en la comunicación de niño a padre. Pero, ¿hay una manera similar de hacerlo de padres a hijos? Por ejemplo, antes de permitir que el usuario agregue un nuevo elemento secundario, quiero que se validen todos los existentes: la lógica de validación está en el elemento secundario, por lo que quiero revisarlos todos y ejecutar, por ejemplo, el método de validación ().

    – Mateusz Bartkowiak

    17/09/2017 a las 18:48

  • Esto responde a la pregunta opuesta a lo que realmente se preguntó. La respuesta de Desmond Lua responde a la pregunta real.

    – Bojan Markovic

    13 de julio de 2018 a las 13:42

  • Event bus es el número 1 en la lista de Chris Fritz de antipatrones vue. Cualquier cosa que se pueda modelar con eventos y estado distribuido se puede modelar con estado global y enlace bidireccional, y en general estará mucho mejor.

    – bbsimonbb

    17 de julio de 2018 a las 7:43

avatar de usuario
wobsoriano

Si terminas aquí buscando Vue 3 script setup

<!-- Parent -->
<template>
    <ChildComponent ref="childComponentRef" />
</template>

<script setup>
import { ref, onMounted } from 'vue'
import ChildComponent from './components/ChildComponent.vue'

const childComponentRef = ref()

onMounted(() => {
    childComponentRef.value.doSomething()
})
</script>
<!-- Child -->
<script setup>
const doSomething = () => {
    console.log('Im batman')
}

// Only available in Vue >= 3.1.3
// No need to import
defineExpose({
    doSomething
})
</script>

Si su versión de Vue es < 3.1.3tendrás que usar setup función y devolver el doSomething función para acceder a ella en el componente principal.

<!-- Child -->
<script>
import { defineComponent } from 'vue'

export default defineComponent({
    setup() {
        const doSomething = () => {
            console.log('Im batman')
        }

        return { doSomething }
    }
})
</script>

  • Deberías usar InstanceType al crear un ref. Asi que const childComponentRef = ref<InstanceType<typeof ChildComponent>>()

    – sanscheese

    14/07/2021 a las 15:30


  • @sanscheese ¿por qué? no se usa texto mecanografiado aquí ni en la pregunta. De todos modos defineExpose() es lo que me perdí. Funcionó instantáneamente después de ponerlo en mi componente secundario.

    – erkage

    12 de diciembre de 2021 a las 16:29


  • sí, tienes que configurar la referencia al dom de destino. el valor ref. representa el dom, y luego recuperó la función secundaria llamando a ref.value.method()

    – BlueboosuphuSKI

    5 de enero a las 6:44

La solución sugerida es para Vue 2, pero si termina aquí buscando una solución API de composición de Vue 3, puede hacer lo siguiente al migrar:

Un componente secundario en una plantilla, que tiene el método “doSomething”:

 <div class="form">                                                                                                                                                        
      <child-component ref="childComponentRef" />                                                                      
</div>  

Con Vue 2:

this.$refs.childComponentRef.doSomething( );
       

Con Vue 3 Composición Api:

    setup( )
    {
        const childComponentRef = ref( );

        childComponentRef.value.doSomething( )

        return {
           childComponentRef
        }
     }  

Tanto el bus de referencia como el de eventos tienen problemas cuando el renderizado de control se ve afectado por v-if. Entonces, decidí ir con un método más simple.

La idea es usar una matriz como una cola para enviar métodos que deben llamarse al componente secundario. Una vez que se montó el componente, procesará esta cola. Vigila la cola para ejecutar nuevos métodos.

(Tomando prestado un código de la respuesta de Desmond Lua)

Código del componente principal:

import ChildComponent from './components/ChildComponent'

new Vue({
  el: '#app',
  data: {
    item: {},
    childMethodsQueue: [],
  },
  template: `
  <div>
     <ChildComponent :item="item" :methods-queue="childMethodsQueue" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.childMethodsQueue.push({name: ChildComponent.methods.save.name, params: {}})
    }
  },
  components: { ChildComponent },
})

Este es el código para ChildComponent

<template>
 ...
</template>

<script>
export default {
  name: 'ChildComponent',
  props: {
    methodsQueue: { type: Array },
  },
  watch: {
    methodsQueue: function () {
      this.processMethodsQueue()
    },
  },
  mounted() {
    this.processMethodsQueue()
  },
  methods: {
    save() {
        console.log("Child saved...")
    },
    processMethodsQueue() {
      if (!this.methodsQueue) return
      let len = this.methodsQueue.length
      for (let i = 0; i < len; i++) {
        let method = this.methodsQueue.shift()
        this[method.name](method.params)
      }
    },
  },
}
</script>

Y hay mucho espacio para mejorar, como mudarse processMethodsQueue a una mezcla…

  • ¿Debe restablecerse la matriz childMethodsQueue para que quede vacía después de ejecutar los métodos?

    – McGrew

    30 oct 2021 a las 23:22

  • Descubrí que debería restablecerse a una matriz vacía. Se necesitan varios otros cambios, pero es demasiado largo para escribir aquí, así que publiqué una respuesta con todos los cambios necesarios para que funcione. Gracias por darme un punto de partida.

    – McGrew

    31 de octubre de 2021 a las 5:04

  • @McGrew Gracias por publicar tu respuesta. Creo que los problemas están relacionados con los cambios en Vue 3. Este código aún funciona en nuestra antigua base de código. De todos modos, creo que estas características debían estar en el marco, y no estoy feliz de haberlo usado de esta manera. Estos son puntos de mejora para Vue.js.

    – mohghaderi

    1 de noviembre de 2021 a las 17:00

avatar de usuario
McGrew

Me gusta la respuesta de mohghaderi, pero me encontré con varios problemas, así que usaré su código de muestra para mostrar los cambios que necesitaba hacer para que funcione. (En mi propio proyecto, estoy usando Vue 3 y la API de opciones).

Código del componente principal de mohghaderi con notas sobre mis cambios:

import ChildComponent from './components/ChildComponent'

new Vue({
  el: '#app',
  data: {
    item: {},
    childMethodsQueue: [],
  },
  // Note: In the template below, I added @child-methods-finished="childMethodsFinished" 
  //       as an event listener, so that we can reset the childMethodsQueue array to
  //       empty once the methods are finished.
  //       If you don't reset it, then the methods stay in there and cause problems.
  template: `
  <div>
     <ChildComponent :item="item" 
                     :methods-queue="childMethodsQueue"
                     @child-methods-finished="childMethodsFinished" />
     <button type="submit" @click.prevent="submit">Post</button>
  </div>
  `,
  methods: {
    submit() {
      this.childMethodsQueue.push({
        name: ChildComponent.methods.save.name,
        params: {}  // Note: delete the {} and put the name of your params, if you use a method that passes in params.
      })
    }
  },
  components: { ChildComponent },
})

Código del componente secundario de mohghaderi con notas sobre mis cambios:

import { objectToString } from "@vue/shared"

export default {
    name: 'ChildComponent',
    props: {
      methodsQueue: { type: Array },
    },
    // Note:  I had to rewrite the watch option because it would not trigger.
    //        You have to add "deep, true" for arrays and objects.
    //        The function has to be called "handler" for it to work as well.
    watch: {
      methodsQueue: {
        handler() {
          this.processMethodsQueue()
        },
        deep: true,
      }
    },
    // Note:  Remove "mounted()" function if you don't want it to run on the mounted event.
    mounted() {
      this.processMethodsQueue()
    },
    methods: {
      save() {
          console.log("Child saved...")
      }, 
      processMethodsQueue() {
        if (!this.methodsQueue) return
        let len = this.methodsQueue.length

        if (!len) return  // Note:  This is required to prevent an infinite loop.
                          //        When we reset the childMethodsQueue array to empty,
                          //        it will trigger this method through the watch option,
                          //        so we need this in order to stop the cycle once we are done.

        // Note:  Instead of using ".shift()" to access an item in the array
        //        we need to use "[i]" otherwise we will get muliple calls of the method
        for (let i = 0; i < len; i++) {
          let method = this.methodsQueue[i]
          this[method.name](method.params)
        }

        // Note:  Now that we are done calling methods, we need to emit an event back to the parent
        //        so it can call it's method to reset the childMethodsQueue array to empty
        this.$emit('child-methods-finished')
      },
    },
  }

  • ¿Debe restablecerse la matriz childMethodsQueue para que quede vacía después de ejecutar los métodos?

    – McGrew

    30 oct 2021 a las 23:22

  • Descubrí que debería restablecerse a una matriz vacía. Se necesitan varios otros cambios, pero es demasiado largo para escribir aquí, así que publiqué una respuesta con todos los cambios necesarios para que funcione. Gracias por darme un punto de partida.

    – McGrew

    31 de octubre de 2021 a las 5:04

  • @McGrew Gracias por publicar tu respuesta. Creo que los problemas están relacionados con los cambios en Vue 3. Este código aún funciona en nuestra antigua base de código. De todos modos, creo que estas características debían estar en el marco, y no estoy feliz de haberlo usado de esta manera. Estos son puntos de mejora para Vue.js.

    – mohghaderi

    1 de noviembre de 2021 a las 17:00

Para comunicar un componente secundario con otro componente secundario, he creado un método en el elemento principal que llama a un método en un elemento secundario con:

this.$refs.childMethod()

Y del otro niño que he llamado al método raíz:

this.$root.theRootMethod()

Funcionó para mí.

  • A esta respuesta le falta mucho de explicación

    – vsync

    6 oct 2020 a las 15:38

¿Ha sido útil esta solución?