Vue v-on: le clic ne fonctionne pas sur le composant

188

J'essaie d'utiliser la directive on click dans un composant mais cela ne semble pas fonctionner. Lorsque je clique sur le composant, rien ne se passe lorsque je devrais avoir un «clic sur un test» dans la console. Je ne vois aucune erreur dans la console, donc je ne sais pas ce que je fais de 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 (le composant)

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

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

Réponses:

335

Si vous souhaitez écouter un événement natif sur l'élément racine d'un composant, vous devez utiliser le modificateur .native pour v-on, comme suit:

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

ou en abrégé, comme suggéré dans le commentaire, vous pouvez également faire:

<template>
  <div id="app">
    <test @click.native="testFunction"></test>
  </div>
</template>
Saurabh
la source
4
Ou la sténographie@click.native="testFunction"
Pier
76
qu'est-ce qu'un événement natif ou en quoi est-il différent des autres événements normaux ? Pourquoi ce cas particulier pour les éléments racine ???
MrClan
24
@MrClan vuejs.org/v2/guide/…
user2875289
9
Il est déconseillé d'utiliser le modificateur natif en vue. Utilisez-le uniquement si c'est absolument nécessaire. Voir @ jim-mcneely pour la manière correcte d'y parvenir, c'est-à-dire émettre des événements à partir de l'élément enfant, puis le réactiver dans le parent.
Deiknymi
5
Voici le lien correct vers les événements natifs
Alexander Kim
32

Je pense que la $emitfonction fonctionne mieux pour ce que je pense que vous demandez. Il garde votre composant séparé de l'instance Vue afin qu'il soit réutilisable dans de nombreux contextes.

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

Utilisez-le en HTML

// Parent component
<test @test-click="testFunction">
Jim McNeely
la source
5
Je pense que c'est la bonne réponse. Gérez au sein du composant la liaison des événements. Ne concerne pas le composant parent de la "version" de l'événement de clic à appeler. En fait, je l'implémente comme réponse ci-dessous dans le composant @click="$emit('click')"et de cette façon, le composant parent utilise simplement le standard@click
Nelson Rodriguez
2
Je suis un peu confus. La partie $ emit est-elle supposée être dans le modèle de composant de test?
Stefan Fabian
1
Ce que @NelsonRodriguez a dit. Utilisez @click="$emit('click')".
Nifel
20

C'est la réponse de @Neps mais avec des détails.


Remarque : la réponse de @ Saurabh est plus adaptée si vous ne souhaitez pas modifier votre composant ou n'y avez pas accès.


Pourquoi @click ne fonctionne-t-il pas?

Les composants sont compliqués. Un composant peut être un petit wrapper de boutons fantaisie, et un autre peut être une table entière avec un tas de logique à l'intérieur. Vue ne sait pas exactement à quoi vous vous attendez lors de la liaison v-modelou de l'utilisation, v-ondonc tout cela devrait être traité par le créateur du composant.

Comment gérer l'événement de clic

Selon la documentation Vue , $emittransmet les événements au parent. Exemple tiré de la documentation:

Dossier principal

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

Composant

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

( @est le v-on raccourci )

Le composant gère l' clickévénement natif et émet le parent@enlarge-text="..."

enlarge-textpeut être remplacé par clickpour donner l'impression que nous gérons un événement de clic natif:

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

Mais ce n'est pas tout. $emitpermet de passer une valeur spécifique avec un événement. Dans le cas de native click, la valeur est MouseEvent (événement JS qui n'a rien à voir avec Vue).

Vue stocke cet événement dans une $eventvariable. Il est donc préférable d'émettre $eventavec un événement pour créer l'impression de l'utilisation de l'événement natif:

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

Un peu verbeux mais voici comment je le fais:

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

Neps
la source
8
où cela va-t-il? Pourquoi le mettez-vous là? Ajoutez un peu plus de détails pour les personnes qui consultent cette réponse, s'il vous plaît?
mix3d
Dans cet exemple, cela serait placé sur la balise div dans le composant Test.vue. Vous pouvez ensuite utiliser v-on: click = "testFunction" ou @ click = "testFunction" lorsque vous utilisez le test de composant dans App.vue
Tim Wickstrom
Je l'ai changé en $emitmais rien ne se passe. Dois-je faire quelque chose en plus de $emit? jsfiddle.net/xwvhy6a3
Richard Barraclough
@RichardBarraclough votre composant émet maintenant votre événement personnalisé "clickTreeItem". La prochaine étape consiste à gérer ce qu'il faut faire avec cet événement dans l'utilisation de ce composant: v-on: myEvent = "myMethod"
Neps
7

Comme mentionné par Chris Fritz (Vue.js Core Team Emeriti ) dans VueCONF US 2019

si Kia entre .nativeet que l'élément racine de l'entrée de base est passé d'une entrée à une étiquette, ce composant est soudainement cassé et ce n'est pas évident et en fait, vous pourriez même ne pas l'attraper tout de suite à moins d'avoir un très bon test. Au lieu de cela, en évitant l'utilisation du .nativemodificateur que je considère actuellement comme un anti-modèle sera supprimé dans Vue 3, vous pourrez définir explicitement que le parent pourrait se soucier de l'élément auquel les écouteurs sont ajoutés ...

Avec Vue 2

En utilisant $listeners :

Donc, si vous utilisez Vue 2, une meilleure option pour résoudre ce problème serait d'utiliser une logique de wrapper entièrement transparente . Pour cela, Vue fournit une $listenerspropriété contenant un objet d'écouteurs utilisés sur le composant. Par exemple:

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

et puis nous avons juste besoin d'ajouter v-on="$listeners"au testcomposant comme:

Test.vue (composant enfant)

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

Maintenant, le <test>composant est un wrapper totalement transparent , ce qui signifie qu'il peut être utilisé exactement comme un <div>élément normal : tous les écouteurs fonctionneront, sans le .nativemodificateur.

Démo:

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>

En utilisant la $emitméthode:

Nous pouvons également utiliser la $emitméthode à cet effet, qui nous aide à écouter les événements des composants enfants dans le composant parent. Pour cela, nous devons d'abord émettre un événement personnalisé à partir du composant enfant comme:

Test.vue (composant enfant)

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

Important: utilisez toujours kebab-case pour les noms d'événements. Pour plus d'informations et une démonstration de ce point, veuillez consulter cette réponse: VueJS passant la valeur calculée du composant au parent .

Maintenant, nous avons juste besoin d'écouter cet événement personnalisé émis dans le composant parent comme:

App.vue

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

Donc, fondamentalement, au lieu de v-on:clickou de la sténographie, @clicknous utiliserons simplement v-on:my-eventou simplement @my-event.

Démo:

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>


Avec Vue 3

Utilisationv-bind="$attrs" :

Vue 3 va nous faciliter la vie à bien des égards. L'un des exemples est qu'il nous aidera à créer un wrapper transparent plus simple avec très moins de configuration en utilisant simplement v-bind="$attrs". En utilisant ceci sur les composants enfants, non seulement notre écouteur fonctionnera directement à partir du parent, mais aussi tout autre attribut fonctionnera également comme s'il était normal<div> .

Donc, en ce qui concerne cette question, nous n'aurons pas besoin de mettre à jour quoi que ce soit dans Vue 3 et votre code fonctionnera toujours correctement, tout comme <div>l'élément racine ici et il écoutera automatiquement tous les événements enfants.

Démo 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="//unpkg.com/vue@next"></script>
<div id="myApp">
  <test v-on:click="testFunction"></test>
</div>

Mais pour les composants complexes avec des éléments imbriqués où nous devons appliquer des attributs et des événements à main <input />au lieu de l'étiquette parent, nous pouvons simplement utiliserv-bind="$attrs"

Démo 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="//unpkg.com/vue@next"></script>
<div id="myApp">
  <base-input 
    label="Search: "
    placeholder="Search"
    @keyup="search">
  </base-input><br/>
</div>

palasн
la source
1
Cela devrait être la réponse acceptée. Merci!
alijunior le
5

Les événements natifs des composants ne sont pas directement accessibles à partir des éléments parents. Au lieu de cela, vous devriez essayer v-on:click.native="testFunction", ou vous pouvez également émettre un événement à partir du Testcomposant. Comme v-on:click="$emit('click')".


la source
0

De la documentation :

En raison des limitations de JavaScript, Vue ne peut pas détecter les modifications suivantes dans un tableau:

  1. Lorsque vous définissez directement un élément avec l'index, par exemple vm.items [indexOfItem] = newValue
  2. Lorsque vous modifiez la longueur du tableau, par exemple vm.items.length = newLength

Dans mon cas, je suis tombé sur ce problème lors de la migration d'Angular vers VUE. Le correctif était assez facile, mais vraiment difficile à trouver:

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