Vue v-on: clique não funciona no componente

186

Estou tentando usar a diretiva on click dentro de um componente, mas ele não parece funcionar. Quando clico no componente, nada acontece quando devo receber um 'teste clicado' no console. Não vejo nenhum erro no console, então não sei o que estou fazendo de errado.

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 (o 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
fonte

Respostas:

331

Se você deseja ouvir um evento nativo no elemento raiz de um componente, use o modificador .native para v-on, como a seguir:

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

ou, como sugerido no comentário, você também pode:

<template>
  <div id="app">
    <test @click.native="testFunction"></test>
  </div>
</template>
Saurabh
fonte
3
Ou o atalho@click.native="testFunction"
Pier
75
o que é um evento nativo ou como ele é diferente de outros eventos normais ? Por que esse caso especial para elementos raiz ???
MrClan
23
@MrClan vuejs.org/v2/guide/…
user2875289
9
é recomendável que o modificador nativo seja usado no vue. Use-o apenas se for absolutamente necessário. Veja @ jim-mcneely para a maneira correta de conseguir isso, isto é, emitindo eventos do elemento filho e depois revivendo-o no pai.
Deiknymi
5
Aqui está o link correto para eventos nativos
Alexander Kim
32

Eu acho que a $emitfunção funciona melhor para o que eu acho que você está pedindo. Ele mantém seu componente separado da instância do Vue, para que seja reutilizável em muitos contextos.

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

Use-o em HTML

// Parent component
<test @test-click="testFunction">
Jim McNeely
fonte
5
Eu acredito que esta seja a resposta correta. Manipule dentro do componente a vinculação dos eventos. Não diga respeito ao componente pai de qual "versão" do evento click chamar. Eu realmente implementá-lo como a resposta abaixo no componente @click="$emit('click')"e de que maneira o componente pai é só usar o regulares@click
Nelson Rodriguez
2
Estou um pouco confuso. Talvez a parte $ emiss esteja no modelo de componente de teste?
Stefan Fabian
1
O que @NelsonRodriguez disse. Use @click="$emit('click')".
Nifel
20

É a resposta do @Neps, mas com detalhes.


Nota : A resposta de @ Saurabh é mais adequada se você não quiser modificar seu componente ou não tiver acesso a ele.


Por que o @click não pode funcionar?

Componentes são complicados. Um componente pode ser um pequeno invólucro de botão sofisticado e outro pode ser uma tabela inteira com um monte de lógica dentro. O Vue não sabe exatamente o que você espera quando vincula v-modelou usav-on portanto tudo isso deve ser processado pelo criador do componente.

Como lidar com o evento de clique

De acordo com os documentos da Vue ,$emit passa eventos para os pais. Exemplo dos documentos:

Arquivo principal

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

Componente

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

( @é a v-on abreviação )

O componente lida com clickeventos nativos e emite os@enlarge-text="..."

enlarge-textpode ser substituído por clickpara parecer que estamos lidando com um evento de clique nativo:

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

Mas isso não é tudo. $emitpermite passar um valor específico com um evento. No caso de nativo click, o valor é MouseEvent (evento JS que não tem nada a ver com o Vue).

O Vue armazena esse evento em uma $eventvariável. Portanto, é melhor emitir $eventum evento para criar a impressão do uso do evento nativo:

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

Um pouco detalhado, mas é assim que eu faço:

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

Neps
fonte
8
onde isso vai? Por que você coloca lá? Adicione um pouco mais de detalhes para as pessoas que visualizam esta resposta, por favor?
precisa saber é o seguinte
Neste exemplo, isso seria colocado na tag div no componente Test.vue. Você pode usar o v-on: click = "testFunction" ou @ click = "testFunction" ao usar o teste de componente no App.vue
Tim Wickstrom
Eu mudei para $emitmas nada está acontecendo. Preciso fazer algo além de $emit? Postado por
Unknown
@RichardBarracough, agora seu componente emite seu evento personalizado "clickTreeItem". O próximo passo é lidar com o que fazer com esse evento no uso desse componente: v-on: myEvent = "myMethod"
Neps
5

Eventos nativos de componentes não são acessíveis diretamente a partir de elementos pai. Em vez disso, você deve tentar v-on:click.native="testFunction"ou pode emitir um evento do Testcomponente também. Like v-on:click="$emit('click')".


fonte
4

Conforme mencionado por Chris Fritz (Vue.js Core Team Emeriti ) no VueCONF US 2019

se tivéssemos Kia .nativeinserido e, em seguida, o elemento raiz da entrada base mudasse de uma entrada para uma etiqueta, de repente esse componente está quebrado e não é óbvio e, de fato, você pode nem mesmo pegá-lo imediatamente, a menos que tenha um teste realmente bom. Em vez disso, evitando o uso do .nativemodificador que atualmente considero um antipadrão será removido no Vue 3, você poderá definir explicitamente que o pai pode se importar com o que os ouvintes de elemento são adicionados ...

Com o Vue 2

Usando $listeners :

Portanto, se você estiver usando o Vue 2, uma opção melhor para resolver esse problema seria usar um lógica de invólucro totalmente transparente . Para isso, o Vue fornece uma $listenerspropriedade que contém um objeto de ouvintes sendo usados ​​no componente. Por exemplo:

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

e só precisamos adicionar v-on="$listeners"aotest componente como:

Test.vue (componente filho)

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

Agora o <test>componente é um invólucro totalmente transparente , o que significa que pode ser usado exatamente como um <div>elemento normal : todos os ouvintes funcionarão sem o .nativemodificador.

Demo:

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 $emit método:

Também podemos usar o $emitmétodo para esse fim, o que nos ajuda a ouvir eventos de componentes filho no componente pai. Para isso, primeiro precisamos emitir um evento personalizado do componente filho, como:

Test.vue (componente filho)

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

Importante: Sempre use kebab-case para nomes de eventos. Para obter mais informações e demonstrações sobre esse ponto, consulte esta resposta: VueJS passando o valor calculado do componente para o pai .

Agora, precisamos apenas ouvir esse evento personalizado emitido no componente pai, como:

App.vue

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

Então, basicamente, em vez de v-on:clickou a abreviação @click, simplesmente usaremos v-on:my-eventou apenas@my-event .

Demo:

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>


Com o Vue 3

Usando v-bind="$attrs" :

O Vue 3 tornará nossa vida muito mais fácil de várias maneiras. Um dos exemplos disso é que ele nos ajudará a criar um invólucro transparente mais simples com muito menos configuração usando apenas v-bind="$attrs". Ao usar isso em componentes filhos, não apenas nosso ouvinte funcionará diretamente do pai, mas também qualquer outro atributo também funcionará como normalmente.<div> .

Portanto, com relação a essa pergunta, não precisaremos atualizar nada no Vue 3 e seu código ainda funcionará bem, como <div>é o elemento raiz aqui, e ele ouvirá automaticamente todos os eventos filhos.

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>

Mas para componentes complexos com elementos aninhados nos quais precisamos aplicar atributos e eventos ao main em <input />vez do rótulo pai, podemos simplesmente 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>

palaşн
fonte
1
Essa deve ser a resposta aceita. Obrigado!
alijunior 04/07
0

A partir da documentação :

Devido a limitações no JavaScript, o Vue não pode detectar as seguintes alterações em uma matriz:

  1. Quando você define diretamente um item com o índice, por exemplo, vm.items [indexOfItem] = newValue
  2. Quando você modifica o comprimento da matriz, por exemplo, vm.items.length = newLength

No meu caso, me deparei com esse problema ao migrar do Angular para o VUE. A correção foi bastante fácil, mas muito difícil de encontrar:

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