Vue v-on: click nie działa na komponencie

186

Próbuję użyć dyrektywy on click wewnątrz komponentu, ale wydaje się, że nie działa. Kiedy klikam komponent, nic się nie dzieje, kiedy powinienem otrzymać „kliknięcie testu” w konsoli. Nie widzę żadnych błędów w konsoli, więc nie wiem, co robię źle.

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 (składnik)

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

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

Odpowiedzi:

333

Jeśli chcesz nasłuchiwać natywnego zdarzenia w elemencie głównym komponentu, musisz użyć rozszerzenia modyfikatora .native dla v-on, na przykład:

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

lub w skrócie, jak zasugerowano w komentarzu, możesz również:

<template>
  <div id="app">
    <test @click.native="testFunction"></test>
  </div>
</template>
Saurabh
źródło
3
Albo skrótowo@click.native="testFunction"
Pier
75
co to jest wydarzenie rodzime lub czym różni się od innych normalnych wydarzeń? Po co ten specjalny przypadek dla elementów głównych ???
MrClan
23
@MrClan vuejs.org/v2/guide/ ...
user2875289
9
natywny modyfikator nie jest zalecany do używania w vue. Używaj go tylko wtedy, gdy jest to absolutnie konieczne. Zobacz @ jim-mcneely, aby dowiedzieć się, jak to osiągnąć, np. Emitowanie zdarzeń z elementu podrzędnego, a następnie przywracanie go do rodzica.
Deiknymi
5
Oto poprawny link do rodzimych wydarzeń
Alexander Kim
32

Myślę, że ta $emitfunkcja działa lepiej dla tego, o co myślę, że prosisz. Dzięki temu komponent jest oddzielony od instancji Vue, dzięki czemu można go ponownie wykorzystać w wielu kontekstach.

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

Użyj go w HTML

// Parent component
<test @test-click="testFunction">
Jim McNeely
źródło
5
Uważam, że to poprawna odpowiedź. Obsługuj w ramach komponentu łączenie zdarzeń. Nie przejmuj się nadrzędnym komponentem „wersji” zdarzenia kliknięcia do wywołania. Właściwie wdrażam to jako odpowiedź poniżej w komponencie @click="$emit('click')"i w ten sposób komponent nadrzędny po prostu używa zwykłego@click
Nelson Rodriguez
2
Jestem trochę zdezorientowany. Czy część $ emit może znajdować się w szablonie komponentu testowego?
Stefan Fabian
1
Co powiedział @NelsonRodriguez. Użyj @click="$emit('click')".
Nifel
20

To odpowiedź @Neps, ale ze szczegółami.


Uwaga : odpowiedź @ Saurabh jest bardziej odpowiednia, jeśli nie chcesz modyfikować swojego komponentu lub nie masz do niego dostępu.


Dlaczego @click nie może po prostu działać?

Komponenty są skomplikowane. Jednym komponentem może być mała, fantazyjna obwoluta przycisku, a innym może być cały stół z mnóstwem logiki w środku. Vue nie wie, czego dokładnie oczekujesz po bindowaniu v-modellub używaniu, v-onwięc wszystko to powinno zostać przetworzone przez twórcę komponentu.

Jak obsługiwać zdarzenie kliknięcia

Zgodnie z docs Vue , $emitprzechodzi zdarzenia do rodzica. Przykład z dokumentów:

Plik główny

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

Składnik

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

( @jest v-on skrótem )

Komponent obsługuje natywne clickzdarzenia i emituje rodzica@enlarge-text="..."

enlarge-textmożna zastąpić, clickaby wyglądało na to, że obsługujemy zdarzenie kliknięcia natywnego:

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

Ale to nie wszystko. $emitumożliwia przekazanie określonej wartości ze zdarzeniem. W przypadku natywnej clickwartością jest MouseEvent (zdarzenie JS, które nie ma nic wspólnego z Vue).

Vue przechowuje to wydarzenie w $eventzmiennej. Najlepiej byłoby więc emitować $eventze zdarzeniem, aby stworzyć wrażenie użycia zdarzenia natywnego:

<button v-on:click="$emit('click', $event)">
  Enlarge text
</button>
OddMorning
źródło
8

Trochę rozwlekłe, ale tak to robię:

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

Neps
źródło
8
dokąd to zmierza? Dlaczego to tam umieszczasz? Dodaj trochę więcej informacji dla osób przeglądających tę odpowiedź, proszę?
mix3d
W tym przykładzie zostanie to umieszczone w znaczniku DIV w komponencie Test.vue. Następnie możesz użyć v-on: click = "testFunction" lub @ click = "testFunction", gdy używasz testu komponentów w App.vue
Tim Wickstrom
Zmieniłem to na, $emitale nic się nie dzieje. Czy muszę coś dodatkowo zrobić $emit? jsfiddle.net/xwvhy6a3
Richard Barraclough
@RichardBarraclough Twój komponent emituje teraz zdarzenie niestandardowe „clickTreeItem”. Następnie zajmiemy się tym, co zrobić z tym zdarzeniem przy użyciu tego komponentu: v-on: myEvent = "myMethod"
Neps
5

Natywne zdarzenia komponentów nie są bezpośrednio dostępne z elementów nadrzędnych. Zamiast tego powinieneś spróbować v-on:click.native="testFunction", lub możesz również wyemitować zdarzenie z Testkomponentu. Lubię v-on:click="$emit('click')".


źródło
4

Jak wspomniał Chris Fritz (Vue.js Core Team Emeriti ) w VueCONF US 2019

gdybyśmy mieli wejście Kia, .nativea następnie element główny wejścia podstawowego zmienił się z wejścia na etykietę, nagle ten element jest zepsuty i nie jest to oczywiste, a tak naprawdę możesz go nawet nie złapać od razu, chyba że masz naprawdę dobry test. Zamiast tego, unikając używania .nativemodyfikatora, który uważam obecnie za anty-wzorzec zostanie usunięty w Vue 3 , będziesz mógł wyraźnie określić, że rodzica może obchodzić, do którego elementu są dodawane słuchacze ...

Dzięki Vue 2

Za pomocą $listeners :

Tak więc, jeśli używasz Vue 2, lepszym rozwiązaniem tego problemu byłoby użycie w pełni przezroczystej logiki opakowującej . W tym przypadku Vue udostępnia $listenerswłaściwość zawierającą obiekt detektorów używanych w komponencie. Na przykład:

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

a następnie wystarczy dodać v-on="$listeners"do testkomponentu:

Test.vue (składnik potomny)

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

Teraz <test>komponent jest w pełni przezroczystym opakowaniem , co oznacza, że ​​można go używać dokładnie tak, jak zwykłego <div>elementu: wszyscy słuchacze będą działać bez .nativemodyfikatora.

Próbny:

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>

Za pomocą $emitmetody:

W $emittym celu możemy również użyć metody, która pomaga nam słuchać zdarzeń komponentów potomnych w komponencie nadrzędnym. W tym celu musimy najpierw wyemitować zdarzenie niestandardowe z komponentu podrzędnego, takie jak:

Test.vue (składnik potomny)

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

Ważne: zawsze używaj kebabów w nazwach wydarzeń. Aby uzyskać więcej informacji i demonstrację dotyczącą tego punktu, sprawdź tę odpowiedź: VueJS przekazuje obliczoną wartość z komponentu do rodzica .

Teraz wystarczy odsłuchać emitowane zdarzenie niestandardowe w komponencie nadrzędnym, takie jak:

App.vue

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

Tak więc, w zasadzie zamiast v-on:clickskrótu lub po @clickprostu użyjemy v-on:my-eventlub po prostu @my-event.

Próbny:

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>


Dzięki Vue 3

Używającv-bind="$attrs" :

Vue 3 znacznie ułatwi nam życie na wiele sposobów. Jednym z przykładów jest to, że pomoże nam to stworzyć prostszą przezroczystą otokę z bardzo mniejszą konfiguracją, po prostu używając v-bind="$attrs". Używając tego na komponentach potomnych, nie tylko nasz odbiornik będzie działał bezpośrednio z rodzica, ale także każdy inny atrybut również będzie działał tak jak normalny<div> .

Tak więc, w odniesieniu do tego pytania, nie będziemy musieli niczego aktualizować w Vue 3, a Twój kod będzie nadal działał dobrze, podobnie jak <div>tutaj element główny i automatycznie nasłuchuje wszystkich zdarzeń podrzędnych.

Demo nr 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>

Ale w przypadku złożonych komponentów z zagnieżdżonymi elementami, w których musimy zastosować atrybuty i zdarzenia do main <input />zamiast do etykiety nadrzędnej, możemy po prostu użyćv-bind="$attrs"

Demo nr 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>

palaѕн
źródło
1
To powinna być akceptowana odpowiedź. Dzięki!
alijunior
0

Z dokumentacji :

Ze względu na ograniczenia JavaScript Vue nie może wykryć następujących zmian w tablicy:

  1. Gdy bezpośrednio ustawiasz element z indeksem, np. Vm.items [indexOfItem] = newValue
  2. Kiedy modyfikujesz długość tablicy, np. Vm.items.length = newLength

W moim przypadku natknąłem się na ten problem podczas migracji z Angular do VUE. Poprawka była dość łatwa, ale naprawdę trudna do znalezienia:

setValue(index) {
    Vue.set(this.arr, index, !this.arr[index]);
    this.$forceUpdate(); // Needed to force view rerendering
}
Andris
źródło