Vue v-on: el clic no funciona en el componente

188

Estoy tratando de usar la directiva de clic dentro de un componente, pero no parece funcionar. Cuando hago clic en el componente, no ocurre nada cuando debo hacer un 'clic de prueba' en la consola. No veo ningún error en la consola, así que no sé qué estoy haciendo mal.

index.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>vuetest</title>
  </head>
  <body>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

App.vue

<template>
  <div id="app">
    <test v-on:click="testFunction"></test>
  </div>
</template>

<script>
import Test from './components/Test'

export default {
  name: 'app',
  methods: {
    testFunction: function (event) {
      console.log('test clicked')
    }
  },
  components: {
    Test
  }
}
</script>

Test.vue (el componente)

<template>
  <div>
    click here
  </div>
</template>

<script>
export default {
  name: 'test',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App'
    }
  }
}
</script>
Javier Cárdenas
fuente

Respuestas:

335

Si desea escuchar un evento nativo en el elemento raíz de un componente, debe usar el modificador .native para v-on, por ejemplo:

<template>
  <div id="app">
    <test v-on:click.native="testFunction"></test>
  </div>
</template>

o en forma abreviada, como se sugiere en el comentario, también puede hacer:

<template>
  <div id="app">
    <test @click.native="testFunction"></test>
  </div>
</template>
Saurabh
fuente
44
O la taquigrafía@click.native="testFunction"
Pier
76
¿Qué es un evento nativo o en qué se diferencia de otros eventos normales ? ¿Por qué este caso especial para los elementos raíz?
MrClan
24
@MrClan vuejs.org/v2/guide/…
user2875289
9
Se desaconseja que el modificador nativo se use en vue. Úselo solo si es absolutamente necesario. Consulte @ jim-mcneely para conocer la forma correcta de lograr esto, es decir, emitir eventos desde el elemento hijo y luego revivirlo en el padre.
Deiknymi
55
Aquí está el enlace correcto a los eventos nativos
Alexander Kim
32

Creo que la $emitfunción funciona mejor para lo que creo que estás pidiendo. Mantiene su componente separado de la instancia de Vue para que sea reutilizable en muchos contextos.

// Child component
<template>
  <div id="app">
    <test @click="$emit('test-click')"></test>
  </div>
</template>

Úselo en HTML

// Parent component
<test @test-click="testFunction">
Jim McNeely
fuente
55
Creo que esta es la respuesta correcta. Manejar dentro del componente la vinculación de los eventos. No le preocupe al componente principal de qué "versión" del evento de clic llamar. De hecho, lo implemento como la respuesta a continuación en el componente @click="$emit('click')"y de esa manera el componente principal solo usa el normal@click
Nelson Rodriguez
2
Estoy un poco confundida. ¿Se supone que la parte $ emitir está en la plantilla del componente de prueba?
Stefan Fabian
1
Lo que dijo @NelsonRodriguez. Uso @click="$emit('click')".
Nifel
20

Es la respuesta de @Neps pero con detalles.


Nota : la respuesta de @ Saurabh es más adecuada si no desea modificar su componente o no tiene acceso a él.


¿Por qué no puede @click simplemente trabajar?

Los componentes son complicados. Un componente puede ser un pequeño envoltorio de botones elegante, y otro puede ser una tabla completa con un montón de lógica dentro. Vue no sabe qué esperas exactamente cuando unes v-modelo usasv-on por lo que todo eso debe ser procesado por el creador del componente.

Cómo manejar el evento click

Según los documentos de Vue ,$emit pasa los eventos a los padres. Ejemplo de documentos:

Archivo principal

<blog-post
  @enlarge-text="onEnlargeText"
/>

Componente

<button @click="$emit('enlarge-text')">
  Enlarge text
</button>

( @es la v-on taquigrafía )

El componente maneja clickeventos nativos y emite los padres@enlarge-text="..."

enlarge-textpuede reemplazarse clickpara que parezca que estamos manejando un evento de clic nativo:

<blog-post
  @click="onEnlargeText"
></blog-post>
<button @click="$emit('click')">
  Enlarge text
</button>

Pero eso no es todo. $emitpermite pasar un valor específico con un evento. En el caso de native click, el valor es MouseEvent (evento JS que no tiene nada que ver con Vue).

Vue almacena ese evento en una $eventvariable. Por lo tanto, sería mejor emitir $eventun evento para crear la impresión del uso de eventos nativos:

<button v-on:click="$emit('click', $event)">
  Enlarge text
</button>
OddMorning
fuente
8

Un poco detallado pero así es como lo hago:

@click="$emit('click', $event)"

Neps
fuente
8
¿A dónde va esto? ¿Por qué lo pones allí? Agregue un poco más de detalles para las personas que ven esta respuesta, por favor.
mix3d
En este ejemplo, esto se colocaría en la etiqueta div en el componente Test.vue. Luego puede usar v-on: click = "testFunction" o @ click = "testFunction" cuando use la prueba de componentes en App.vue
Tim Wickstrom el
Lo cambié a $emitpero no pasa nada. ¿Necesito hacer algo además de $emit? jsfiddle.net/xwvhy6a3
Richard Barraclough
@RichardBarraclough su componente ahora emite su evento personalizado "clickTreeItem". Lo siguiente es manejar qué hacer con ese evento en el uso de ese componente: v-on: myEvent = "myMethod"
Neps
6

Como mencionó Chris Fritz (Vue.js Core Team Emeriti ) en VueCONF US 2019

si hiciéramos que Kia ingresara .nativey luego el elemento raíz de la entrada base cambiara de una entrada a una etiqueta, de repente este componente se rompe y no es obvio y, de hecho, es posible que ni siquiera lo atrape de inmediato a menos que tenga una prueba realmente buena. En cambio, al evitar el uso del .nativemodificador, que actualmente considero que un antipatrón se eliminará en Vue 3 , podrá definir explícitamente que el padre podría preocuparse sobre a qué elemento se agregan los oyentes ...

Con Vue 2

Utilizando $listeners:

Entonces, si está utilizando Vue 2, una mejor opción para resolver este problema sería usar una lógica de envoltura totalmente transparente . Para esto, Vue proporciona una $listenerspropiedad que contiene un objeto de oyentes que se utilizan en el componente. Por ejemplo:

{
  focus: function (event) { /* ... */ }
  input: function (value) { /* ... */ },
}

y luego solo necesitamos agregar v-on="$listeners"altest componente como:

Test.vue (componente hijo)

<template>
  <div v-on="$listeners">
    click here
  </div>
</template>

Ahora el <test>componente es un contenedor totalmente transparente , lo que significa que se puede usar exactamente como un <div>elemento normal : todos los oyentes funcionarán, sin el .nativemodificador.

Manifestación:

Vue.component('test', {
  template: `
    <div class="child" v-on="$listeners">
      Click here
    </div>`
})

new Vue({
  el: "#myApp",
  data: {},
  methods: {
    testFunction: function(event) {
      console.log('test clicked')
    }
  }
})
div.child{border:5px dotted orange; padding:20px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
  <test @click="testFunction"></test>
</div>

Utilizando el $emitmétodo:

También podemos usar el $emitmétodo para este propósito, que nos ayuda a escuchar eventos de componentes secundarios en el componente principal. Para esto, primero debemos emitir un evento personalizado desde un componente secundario como:

Test.vue (componente hijo)

<test @click="$emit('my-event')"></test>

Importante: siempre use kebab-case para los nombres de los eventos. Para obtener más información y una demostración de este punto, consulte esta respuesta: VueJS pasa el valor calculado del componente al padre .

Ahora, solo necesitamos escuchar este evento personalizado emitido en el componente principal como:

App.vue

<test @my-event="testFunction"></test>

Entonces, básicamente en lugar de v-on:clicko la taquigrafía @clicksimplemente usaremos v-on:my-evento simplemente @my-event.

Manifestación:

Vue.component('test', {
  template: `
    <div class="child" @click="$emit('my-event')">
      Click here
    </div>`
})

new Vue({
  el: "#myApp",
  data: {},
  methods: {
    testFunction: function(event) {
      console.log('test clicked')
    }
  }
})
div.child{border:5px dotted orange; padding:20px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.min.js"></script>
<div id="myApp">
  <test @my-event="testFunction"></test>
</div>


Con Vue 3

Utilizando v-bind="$attrs":

Vue 3 nos hará la vida mucho más fácil de muchas maneras. Uno de los ejemplos es que nos ayudará a crear un contenedor transparente más simple con muy poca configuración simplemente usandov-bind="$attrs" . Al usar esto en componentes secundarios, no solo nuestro oyente trabajará directamente desde el padre, sino que también cualquier otro atributo también funcionará de manera normal <div>.

Entonces, con respecto a esta pregunta, no necesitaremos actualizar nada en Vue 3 y su código seguirá funcionando bien, ya que <div>es el elemento raíz aquí y escuchará automáticamente todos los eventos secundarios.

Demo # 1:

const { createApp } = Vue;

const Test = {
  template: `
    <div class="child">
      Click here
    </div>`
};

const App = {
  components: { Test },
  setup() {
    const testFunction = event => {
      console.log("test clicked");
    };
    return { testFunction };
  }
};

createApp(App).mount("#myApp");
div.child{border:5px dotted orange; padding:20px;}
<script src="//unpkg.com/vue@next"></script>
<div id="myApp">
  <test v-on:click="testFunction"></test>
</div>

Pero para componentes complejos con elementos anidados donde necesitamos aplicar atributos y eventos a main en <input />lugar de a la etiqueta principal, simplemente podemos usarv-bind="$attrs"

Demo # 2:

const { createApp } = Vue;

const BaseInput = {
  props: ['label', 'value'],
  template: `
    <label>
      {{ label }}
      <input v-bind="$attrs">
    </label>`
};

const App = {
  components: { BaseInput },
  setup() {
    const search = event => {
      console.clear();
      console.log("Searching...", event.target.value);
    };
    return { search };
  }
};

createApp(App).mount("#myApp");
input{padding:8px;}
<script src="//unpkg.com/vue@next"></script>
<div id="myApp">
  <base-input 
    label="Search: "
    placeholder="Search"
    @keyup="search">
  </base-input><br/>
</div>

palarse
fuente
1
Esta debería ser la respuesta aceptada. ¡Gracias!
alijunior
5

No se puede acceder directamente a los eventos nativos de los componentes desde los elementos principales. En su lugar, debería intentarlo v-on:click.native="testFunction", o también puede emitir un evento desde el Testcomponente. Al igual v-on:click="$emit('click')".


fuente
0

De la documentación :

Debido a limitaciones en JavaScript, Vue no puede detectar los siguientes cambios en una matriz:

  1. Cuando configura directamente un elemento con el índice, por ejemplo, vm.items [indexOfItem] = newValue
  2. Cuando modifica la longitud de la matriz, por ejemplo, vm.items.length = newLength

En mi caso, me topé con este problema al migrar de Angular a VUE. La solución fue bastante fácil, pero realmente difícil de encontrar:

setValue(index) {
    Vue.set(this.arr, index, !this.arr[index]);
    this.$forceUpdate(); // Needed to force view rerendering
}
Andris
fuente