Búsqueda de sitios web

javascript - Vue v-on:click no funciona en el componente


Estoy tratando de usar la directiva on click dentro de un componente, pero parece que no funciona. Cuando hago clic en el componente no pasa nada, cuando debería aparecer un 'test clicked' 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>

Respuesta #1

Si quieres escuchar un evento nativo en el elemento raíz de un componente, tienes que usar el modificador .native para v-on, como sigue:

<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 puedes hacer:

<template>
  <div id="app">
    <test @click.native="testFunction"></test>
  </div>
</template>

Referencia para leer más sobre evento nativo

Respuesta #2

Creo que la función $emit funciona mejor para lo que creo que estás preguntando. Mantiene tu 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>

Úsalo en HTML:

<!-- Parent component -->
<test @test-click="testFunction">

Respuesta #3

Es la respuesta de @Neps pero con detalles.


Nota: La respuesta de @Saurabh es más adecuada si no quieres modificar tu componente o no tienes acceso a él.


¿Por qué @click no puede simplemente funcionar?

Los componentes son complicados. Un componente puede ser un pequeño envoltorio de botón elegante, y otro puede ser toda una tabla con un montón de lógica dentro. Vue no sabe exactamente qué esperas cuando enlazas v-model o usas v-on, por lo que todo eso debería ser procesado por el creador del componente.

Cómo manejar el evento de clic

Según la documentación de Vue, $emit pasa eventos al componente padre. Ejemplo de la documentación:

Archivo principal

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

Componente

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

(@ es la abreviatura de v-on)

El componente maneja el evento nativo click y emite @enlarge-text="..." del padre

enlarge-text se puede reemplazar con click para que parezca que estamos manejando un evento de clic nativo:

<blog-post @click="onEnlargeText">
  Enlarge text
</blog-post>

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

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

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

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

Respuesta #4

Como mencionó Chris Fritz (miembro emérito del equipo central de Vue.js) en VueCONF US 2019

Si hiciéramos que Kia ingresara .native y luego el elemento raíz del input base cambiara de un input a una etiqueta, de repente este componente se rompe y no es evidente, y de hecho, puede que ni siquiera lo detectes de inmediato a menos que tengas una prueba realmente buena. En cambio, al evitar el uso del modificador .native , podrás definir explícitamente que al padre le pueda importar a qué elemento se agregan los listeners...

Con Vue 2

Usando $listeners:

Entonces, si estás usando Vue 2, una mejor opción para resolver este problema sería usar una lógica de envoltorio totalmente transparente. Para esto, Vue proporciona una propiedad $listeners que contiene un objeto de escuchadores que se están usando en el componente. Por ejemplo:

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

y luego solo necesitamos agregar v-on="$listeners" al componente test así:

Test.vue (componente hijo)

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

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

Demostració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>

Usando el método $emit:

También podemos usar el método $emit para este propósito, lo que nos ayuda a escuchar los eventos de un componente hijo en el componente padre. Para esto, primero necesitamos emitir un evento personalizado desde un componente hijo, como:

Test.vue (componente hijo)

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

Importante: Siempre use kebab-case para los nombres de los eventos. Para más información y una demostración sobre este punto, por favor consulte esta respuesta: VueJS pasando un valor calculado del componente al padre.

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

App.vue

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

Así que básicamente, en lugar de v-on:click o el atajo @click, simplemente usaremos v-on:my-event o simplemente @my-event.

Demostració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

Usando v-bind="$attrs":

Vue 3 va a hacer nuestra vida mucho más fácil en muchos sentidos. Un ejemplo es que nos ayudará a crear un envoltorio transparente más simple con menos configuración, usando solo v-bind="$attrs". Al usar esto en componentes hijos, no solo nuestro listener funcionará directamente desde el padre, sino que también cualquier otro atributo funcionará igual que lo haría con un <div> normal.

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

Demostración #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="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.8/vue.global.min.js"></script>
<div id="myApp">
  <test v-on:click="testFunction"></test>
</div>

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

Demostración #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="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.8/vue.global.min.js"></script>
<div id="myApp">
  <base-input 
    label="Search: "
    placeholder="Search"
    @keyup="search">
  </base-input><br/>
</div>

Respuesta #5

Un poco verboso, pero así es como lo hago:

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

ACTUALIZACIÓN: Ejemplo añadido por @sparkyspider

<div-container @click="doSomething"></div-container>

En el componente div-container...

<template>
  <div @click="$emit('click', $event);">The inner div</div>
</template>

Artículos relacionados