vuejs actualiza los datos principales del componente secundario

197

Estoy empezando a jugar con vuejs (2.0). Construí una página simple con un componente. La página tiene una instancia de Vue con datos. En esa página me registré y agregué el componente a html. El componente tiene uno input[type=text]. Quiero que ese valor se refleje en el padre (instancia principal de Vue).

¿Cómo actualizo correctamente los datos principales del componente? Pasar un accesorio vinculado del padre no es bueno y arroja algunas advertencias a la consola. Tienen algo en su documento pero no funciona.

3
220

El enlace bidireccional ha quedado obsoleto en Vue 2.0 a favor de utilizar una arquitectura más impulsada por eventos. En general, un niño no debe mutar sus accesorios. Más bien, debe hacer $emiteventos y dejar que los padres respondan a esos eventos.

En su caso específico, podría usar un componente personalizado con v-model. Esta es una sintaxis especial que permite algo parecido al enlace bidireccional, pero en realidad es una forma abreviada de la arquitectura impulsada por eventos descrita anteriormente. Puede leer sobre esto aquí -> https://vuejs.org/v2/guide/components.html#Form-Input-Components-using-Custom-Events .

He aquí un ejemplo sencillo:

Vue.component('child', {
  template: '#child',
  
  //The child has a prop named 'value'. v-model will automatically bind to this prop
  props: ['value'],
  methods: {
    updateValue: function (value) {
      this.$emit('input', value);
    }
  }
});

new Vue({
  el: '#app',
  data: {
    parentValue: 'hello'
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<div id="app">
  <p>Parent value: {{parentValue}}</p>
  <child v-model="parentValue"></child>
</div>

<template id="child">
   <input type="text" v-bind:value="value" v-on:input="updateValue($event.target.value)">
</template>

Los documentos afirman que

<custom-input v-bind:value="something" v-on:input="something = arguments[0]"></custom-input>

es equivalente a

<custom-input v-model="something"></custom-input>

Es por eso que el accesorio en el niño necesita ser nombrado valor, y por qué el niño necesita $ emitir un evento llamado input.

14
  • primero gracias por la respuesta. ¿Puede expandir, o mejor señalar documentos sobre el evento de 'entrada'? parece un evento incorporado. Gal Ziv 1 dic 2016 a las 17:07
  • 2
    He añadido una aclaración y he hecho más obvio el enlace a la documentación. asemahle 1 de diciembre de 2016 a las 17:58
  • 2
    Omití el prop "valor" para el componente y la función creada y todavía funciona. ¿Puedes explicar por qué lo usaste? xetra11 2 de marzo de 2017 a las 8:45
  • 2
    Si no agrega el accesorio, será undefinedhasta el primer cambio. Vea este violín donde he comentado props: ['value']. Observe cómo es el valor inicial undefined, en lugar de hello: jsfiddle.net/asemahle/8Lrkfxj6 . Después del primer cambio, Vue agrega dinámicamente una propuesta de valor al componente, por lo que funciona. asemahle 2 mar 2017 a las 16:24
  • Leí más allá de esto en los documentos. Gran ejemplo de trabajo. +10 si pudiera! clay 13/04/2017 a las 15:06
154

En componente hijo:

this.$emit('eventname', this.variable)

En componente padre:

<component @eventname="updateparent"></component>

methods: {
    updateparent(variable) {
        this.parentvariable = variable
    }
}
7
  • 13
    Mi mente está simplemente impresionado por este ejemplo. No tienes idea de cuántos tutoriales pasé antes de llegar aquí ...suchislife 9/09/19 a las 15:48
  • @Sarvar Nishonboev: cuando el método updateparent actualiza la variable parent, ¿la devuelve "automáticamente" al niño como accesorio? veritas 1 de septiembre de 2020 a las 6:14
  • 1
    @veritas - no, no en este caso porque el componente secundario no recibe parentvariable como prop. Si lo hiciera, entonces lo haría. jomofrodo 21/10/20 a las 2:43
  • 1
    Este ejemplo es más asombroso que los demás. Bcktr 9 de marzo a las 4:15
  • 1
    Lo difícil es simplificarlo. ¡Muchas gracias! Por qué otras respuestas largas lo hacen tan complicado. Siwei 21 abr a las 1:06
143

De la documentación :

In Vue.js, the parent-child component relationship can be summarized as props down, events up. The parent passes data down to the child via props, and the child sends messages to the parent via events. Let’s see how they work next.

ingrese la descripción de la imagen aquí

Cómo pasar accesorios

A continuación se muestra el código para pasar accesorios a un elemento secundario:

<div>
  <input v-model="parentMsg">
  <br>
  <child v-bind:my-message="parentMsg"></child>
</div>

Cómo emitir evento

HTML:

<div id="counter-event-example">
  <p>{{ total }}</p>
  <button-counter v-on:increment="incrementTotal"></button-counter>
  <button-counter v-on:increment="incrementTotal"></button-counter>
</div>

JS:

Vue.component('button-counter', {
  template: '<button v-on:click="increment">{{ counter }}</button>',
  data: function () {
    return {
      counter: 0
    }
  },
  methods: {
    increment: function () {
      this.counter += 1
      this.$emit('increment')
    }
  },
})
new Vue({
  el: '#counter-event-example',
  data: {
    total: 0
  },
  methods: {
    incrementTotal: function () {
      this.total += 1
    }
  }
})
4
  • 5
    ¿Qué pasa si la función "incremento" estaba en el componente principal y quería activarla desde el componente secundario? Hamzaouiii 10/04/18 a las 14:01
  • el momento cortante de captar el concepto, aunque lo he estado usando varias veces por hacky copy paste ...Yordan Georgiev 3 de agosto de 2018 a las 14:00
  • 1
    Imprimiré ese gráfico y lo pegaré en mi cabeza. ¡Gracias! domih 7 de marzo de 2019 a las 13:10
  • 1
    En este ejemplo, ¿no deberíamos tener un detector de eventos definido en el componente raíz? Algo como: `` `montado () {this.on ('incremento', () => {this.incrementTotal ();}); } `` `jmk2142 7 feb.20 a las 16:35
28

Componente hijo

Úselo this.$emit('event_name')para enviar un evento al componente principal.

ingrese la descripción de la imagen aquí

Componente principal

Para escuchar ese evento en el componente padre, lo hacemos v-on:event_namey ocurre un método ( ex. handleChange) que queremos ejecutar en ese evento

ingrese la descripción de la imagen aquí

Hecho :)

16

Estoy de acuerdo con la emisión de eventos y las respuestas del modelo v para los anteriores. Sin embargo, pensé en publicar lo que encontré sobre componentes con múltiples elementos de formulario que quieren emitir de nuevo a su padre, ya que este parece ser uno de los primeros artículos devueltos por Google.

Sé que la pregunta especifica una sola entrada, pero esta parecía la coincidencia más cercana y podría ahorrarle a la gente algo de tiempo con componentes vue similares. Además, nadie ha mencionado .syncaún el modificador.

Hasta donde yo sé, la v-modelsolución solo es adecuada para una entrada que regresa a su padre. Me tomó un poco de tiempo buscarlo, pero la documentación de Vue (2.3.0) muestra cómo sincronizar múltiples accesorios enviados al componente de nuevo al padre (a través de emitir, por supuesto).

Se le llama apropiadamente .syncmodificador.

Esto es lo que dice la documentación :

In some cases, we may need “two-way binding” for a prop. Unfortunately, true two-way binding can create maintenance issues, because child components can mutate the parent without the source of that mutation being obvious in both the parent and the child.

That’s why instead, we recommend emitting events in the pattern of update:myPropName. For example, in a hypothetical component with a title prop, we could communicate the intent of assigning a new value with:

this.$emit('update:title', newTitle)

Then the parent can listen to that event and update a local data property, if it wants to. For example:

<text-document   
 v-bind:title="doc.title"  
 v-on:update:title="doc.title = $event"
></text-document>

For convenience, we offer a shorthand for this pattern with the .sync modifier:

<text-document v-bind:title.sync="doc.title"></text-document>

También puede sincronizar varios a la vez enviando a través de un objeto. Consulta la documentación aquí.

2
  • Esto es lo que estaba buscando. Muchas gracias. Thomas 23 de mayo de 2019 a las 10:08
  • Esta es la mejor y más actualizada solución a partir de 2020. ¡Muchas gracias! Marcelo 25/01/20 a las 13:22
12

La forma más sencilla es usar this.$emit

Father.vue

<template>
  <div>
    <h1>{{ message }}</h1>
    <child v-on:listenerChild="listenerChild"/>
  </div>
</template>

<script>
import Child from "./Child";
export default {
  name: "Father",
  data() {
    return {
      message: "Where are you, my Child?"
    };
  },
  components: {
    Child
  },
  methods: {
    listenerChild(reply) {
      this.message = reply;
    }
  }
};
</script>

Child.vue

<template>
  <div>
    <button @click="replyDaddy">Reply Daddy</button>
  </div>
</template>

<script>
export default {
  name: "Child",
  methods: {
    replyDaddy() {
      this.$emit("listenerChild", "I'm here my Daddy!");
    }
  }
};
</script>

Mi ejemplo completo: https://codesandbox.io/s/update-parent-property-ufj4b

2
  • 1
    Eso me salvó la vida. ¡¡¡¡Gracias!!!! Daisy 11 de marzo a las 5:20
  • este es el mejor y más simple, me ahorras tiempo muchas graciasRachid Dev 29 de junio a las 18:25
6

También es posible pasar props como Object o Array. En este caso, los datos se vincularán bidireccionalmente:

(Esto se indica al final del tema: https://vuejs.org/v2/guide/components.html#One-Way-Data-Flow )

Vue.component('child', {
  template: '#child',
  props: {post: Object},
  methods: {
    updateValue: function () {
      this.$emit('changed');
    }
  }
});

new Vue({
  el: '#app',
  data: {
    post: {msg: 'hello'},
    changed: false
  },
  methods: {
    saveChanges() {
        this.changed = true;
    }
  }
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.13/vue.js"></script>

<div id="app">
  <p>Parent value: {{post.msg}}</p>
  <p v-if="changed == true">Parent msg: Data been changed - received signal from child!</p>
  <child :post="post" v-on:changed="saveChanges"></child>
</div>

<template id="child">
   <input type="text" v-model="post.msg" v-on:input="updateValue()">
</template>
0
4

En componente principal ->

data : function(){
            return {
                siteEntered : false, 
            };
        },

En componente hijo ->

this.$parent.$data.siteEntered = true;

1

La forma correcta es a $emit()un evento en el componente secundario que escucha la instancia principal de Vue .

// Child.js
Vue.component('child', {
  methods: {
    notifyParent: function() {
      this.$emit('my-event', 42);
    }
  }
});

// Parent.js
Vue.component('parent', {
  template: '<child v-on:my-event="onEvent($event)"></child>',
  methods: {
    onEvent: function(ev) {
      v; // 42
    }
  }
});
1

En el niño

 <input
            type="number"
            class="form-control"
            id="phoneNumber"
            placeholder
            v-model="contact_number"
            v-on:input="(event) => this.$emit('phoneNumber', event.target.value)"
    />

data(){
    return {
      contact_number : this.contact_number_props
    }
  },
  props : ['contact_number_props']

En padre

<contact-component v-on:phoneNumber="eventPhoneNumber" :contact_number_props="contact_number"></contact-component>


 methods : {
     eventPhoneNumber (value) {
      this.contact_number = value
    }
1

RESPUESTA 2021 - Vue 2.3+

RESPUESTA BREVE: Simplemente agregue el .syncmodificador en el padre y pase los datos como accesorios a los hijos:

    // PARENT:
    data () {
    return {
      formData: {
        members: [] //<- we wanna pass this one down to children and add/remove from the child component
      }
    }

   // PARENT TEMPLATE:
   <!-- ADD MEMBERS -->
  <add-members :members.sync="formData.members" />

Componente hijo anidado: AddMembers.vue

export default {
  name: 'AddMembers',
  props: ['members'],
  methods: {
    addMember () {
      this.members.push(new Member()) // <-- you can play and reactivity will work (in the parent)  
    },
    removeMember (index) {
      console.log('remove', index, this.members.length < 1)
      this.members.splice(index, 1)
    }
  }
}

Larga historia: los cambios del componente hijo en realidad se están emitiendo $ y actualizando dataForm.memmbers [] del padre.

fuente: Mauro Perez en medio

0

Otra forma es pasar una referencia de su establecedor desde el padre como un accesorio al componente hijo, de manera similar a como lo hacen en React. Por ejemplo, que tiene un método updateValueen el padre para actualizar el valor, se puede crear una instancia del componente secundario de este modo: <child :updateValue="updateValue"></child>. Luego, el niño tendrá un apoyo correspondiente: props: {updateValue: Function}y en la plantilla de llamar al método cuando cambia la entrada: <input @input="updateValue($event.target.value)">.

0

No sé por qué, pero acabo de actualizar con éxito los datos de los padres con el uso de datos como objeto, :set&computed

Parent.view

<!-- check inventory status - component -->
    <CheckInventory :inventory="inventory"></CheckInventory>

data() {
            return {
                inventory: {
                    status: null
                },
            }
        },

Child.vue

<div :set="checkInventory">

props: ['inventory'],

computed: {
            checkInventory() {

                this.inventory.status = "Out of stock";
                return this.inventory.status;

            },
        }
0

este ejemplo le dirá cómo pasar el valor de entrada a los padres en el botón de envío.

Primero defina eventBus como nuevo Vue.

//main.js
import Vue from 'vue';
export const eventBus = new Vue();

Pass your input value via Emit.
//Sender Page
import { eventBus } from "../main";
methods: {
//passing data via eventbus
    resetSegmentbtn: function(InputValue) {
        eventBus.$emit("resetAllSegment", InputValue);
    }
}

//Receiver Page
import { eventBus } from "../main";

created() {
     eventBus.$on("resetAllSegment", data => {
         console.log(data);//fetching data
    });
}
0

Creo que esto hará el truco:

@change="$emit(variable)"

0

Intro

Estaba buscando enviar datos de padre a hijo (y viceversa) en vue3 (sé que la pregunta era sobre vue2, pero no hay referencias para vue3 en SO en ese momento).

A continuación se muestra el resultado estándar de trabajo, puro "html + js", sin empaquetadores, módulos, etc., con algunas advertencias que tenía, explicado.

Notas:

  1. Inserción del niño - línea
    <component-a :foo="bar" @newfooevent="bar = $event"></component-a>`
    
  • Me comprometo parent.bara child.foousar taquigrafía :foo="bar", igual que v-bind:foo="bar". Pasa datos de padres a hijos a través de accesorios.

  • Advertencia: el detector de eventos debe colocarse solo en la etiqueta del componente secundario.

    Esa es la @newfooevent="bar = $event"parte.

    No puede captar la señal en el <div id="app">ni en ningún otro lugar dentro del padre.

    Aún así, este es el lado del universo de los padres, y aquí puede acceder a todos los datos de los padres y extraer los datos de la señal del niño para lidiar con ellos.

  1. Puede crear una aplicación y definir el componente después de ella (la app.component("component-a", ...)pieza.

    Advertencia: no es necesario declarar de antemano los componentes , por ejemplo, funciones en C / C ++. Puede crear una aplicación que utilice el componente y definir el componente posteriormente. Perdí mucho tiempo buscando la manera de declararlo de alguna manera, no es necesario.

  2. Aquí puede encontrar un buen ejemplo del v-modeluso y el código que utilicé para resolver las cosas: https://javascript.plainenglish.io/vue-3-custom-events-d2f310fe34c9

El ejemplo

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>App</title>
    <meta charset="utf-8" />
    <script src="https://unpkg.com/[email protected]"></script>
  </head>
  <body>
    <div id="app">
      <component-a :foo="bar" @newfooevent="bar = $event"></component-a>
      <p>Parent copy of `bar`: {{ bar }}</p>
      <button @click="bar=''">Clear</button>
    </div>

    <script>
      const app = Vue.createApp({
        data() {
          return {
            bar: "bar start value"
          };
        }
      });      

      app.component("component-a", {
        props: {
          foo: String
        },
        template: `
          <input 
            type="text"
            :value="foo"
            @input="$emit('newfooevent', $event.target.value)">
        `
      });      

      app.mount("#app");
    </script>
  </body>
</html>