Vue v-on: нажатие не работает на компоненте

Я пытаюсь использовать директиву on click внутри компонента, но, похоже, она не работает. Когда я нажимаю на компонент, ничего не происходит, когда я должен получить «тестовый щелчок» в консоли. Я не вижу ошибок в консоли, поэтому не знаю, что делаю не так.

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 (компонент)

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

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

person Javier Cárdenas    schedule 05.01.2017    source источник


Ответы (7)


Если вы хотите прослушивать собственное событие в корневом элементе компонента, вы должны использовать . Native модификатор для v-on, например:

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

или сокращенно, как предлагается в комментарии, вы также можете:

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

Ссылка для чтения подробнее о нативном событии

person Saurabh    schedule 05.01.2017
comment
Или сокращение @click.native="testFunction" - person Pier; 17.04.2017
comment
что такое собственное событие или чем оно отличается от других нормальных событий? Почему этот особый случай для корневых элементов ??? - person MrClan; 22.07.2017
comment
@MrClan vuejs.org/v2/guide/ - person user2875289; 26.07.2017
comment
Не рекомендуется использовать модификатор native в vue. Используйте его только в случае крайней необходимости. См. @ Jim-mcneely, чтобы узнать о правильном способе достижения этого, т. Е. Испускать события из дочернего элемента и затем восстанавливать его в родительском. - person Deiknymi; 13.06.2018
comment
@Dimensionless Почему это не рекомендуется? Не могли бы вы дать ссылку или уточнить? - person WoodrowShigeru; 09.08.2018
comment
Когда v-on используется в элементе HTML, это в основном addEventListener для событий собственного HTML-элемента, потому что это единственный вид событий, которые имеют эти элементы. Но при использовании в компоненте Vue он вместо этого является слушателем событий Vue, $emit-полученным от этого компонента, например this.$emit('click') или this.$emit('whateverEventNameYouUse'). (это должно было быть редактированием ответа, но было отклонено) - person stsloth; 12.08.2018
comment
Моя testFunction выполняется, когда открыты параметры разработчика Chrome. Если я закрою инструменты разработчика, метод не будет вызван. Есть мысли по этому поводу? - person Balaji Kartheeswaran; 03.02.2019
comment
@Dimensionless, почему это не рекомендуется? Если я хочу, чтобы мой компонент имел определенное настраиваемое поведение при нажатии, я не вижу причин, по которым мой компонент хотел бы знать об этом. - person Robo Robok; 19.09.2019
comment
@RoboRobok В его использовании нет ничего плохого, просто он требует большего внимания. Вы можете использовать собственный модификатор, чтобы привязать событие к корневому элементу компонента, но при этом вы не указываете внутри компонента, что есть событие в корневом элементе компонента. Допустим, у вас есть компонент ввода, и вы слушаете событие фокуса с помощью собственных модификаторов, а в будущем вы реорганизуете компонент и заключите элемент ввода с помощью div. Теперь не будет события фокуса, и событие никогда не будет запущено. - person Deiknymi; 20.09.2019
comment
@Dimensionless имеет смысл для focus, хотя не уверен, что такое общее событие, как click. - person Robo Robok; 20.09.2019
comment
Я также не понимаю, почему native требуется для click. например. с компонентом ‹Button›, почему я должен определять действие щелчка внутри компонента, а не в корне? - person nyusternie; 25.01.2020
comment
Предупреждение в режиме разработчика Vue.js было бы неплохо, вот и все. - person AndrewJC; 05.06.2020

Я думаю, что функция $emit лучше подходит для того, о чем, я думаю, вы просите. Он отделяет ваш компонент от экземпляра Vue, поэтому его можно использовать повторно во многих контекстах.

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

Используйте это в HTML

// Parent component
<test @test-click="testFunction">
person Jim McNeely    schedule 02.12.2017
comment
Я считаю, что это правильный ответ. Обрабатывайте в компоненте привязку событий. Не касайтесь родительского компонента, какую версию события щелчка вызывать. Я фактически реализую это как ответ ниже в компоненте @click="$emit('click')", и таким образом родительский компонент просто использует обычный @click - person Nelson Rodriguez; 28.08.2018
comment
Я немного запутался. Может быть, часть $ emit должна быть в шаблоне тестового компонента? - person Stefan Fabian; 21.11.2018
comment
Что сказал @NelsonRodriguez. Используйте 1_. - person Nifel; 10.12.2019
comment
Это определенно более явное решение по сравнению с модификатором .native, которое делает родительский компонент менее зависимым от деталей реализации дочернего элемента, что почти всегда является плюсом. - person zcoop98; 24.05.2021

Это ответ @Neps, но с подробностями.


Примечание. Ответ @ Saurabh более подходит, если вы не хотите изменять свой компонент или нет к нему доступа.


Почему @click не может просто работать?

Компоненты сложные. Один компонент может быть небольшой причудливой оберткой кнопки, а другой может быть целой таблицей с кучей логики внутри. Vue не знает, что именно вы ожидаете при связывании v-model или использовании v-on, поэтому все это должно обрабатываться создателем компонента.

Как обрабатывать событие клика

Согласно документации Vue, $emit проходит события для родителей. Пример из документов:

Главный файл

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

Компонент

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

(@ - это v-on сокращение)

Компонент обрабатывает собственное click событие и выдает @enlarge-text="..." родительского

enlarge-text можно заменить на click, чтобы было похоже, что мы обрабатываем собственное событие щелчка:

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

Но это не все. $emit позволяет передавать определенное значение с событием. В случае собственного click значением является MouseEvent (JS событие, не имеющее ничего общего с Vue).

Vue сохраняет это событие в переменной $event. Итак, было бы лучше всего создать $event с событием, чтобы создать впечатление использования собственного события:

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

Как упоминалось Крисом Фрицем (Vue.js Core Team Emeriti) в VueCONF US 2019

если бы Kia ввела .native, а затем корневой элемент базового ввода изменился с ввода на метку, внезапно этот компонент сломался, и это не очевидно, и на самом деле вы могли бы даже не уловить его сразу, если у вас нет действительно хорошего теста . Вместо этого, избегая использования модификатора .native который я в настоящее время считаю анти-шаблоном, будет удален в Vue 3, вы сможете явно определить, что родитель может заботиться о том, какие слушатели элементов добавляются к ...

С Vue 2

Использование $listeners:

Итак, если вы используете Vue 2, лучшим вариантом решения этой проблемы было бы использование логики полностью прозрачной оболочки. Для этого Vue предоставляет свойство $listeners, содержащее объект слушателей, используемых в компоненте. Например:

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

а затем нам просто нужно добавить v-on="$listeners" к компоненту test, например:

Test.vue (дочерний компонент)

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

Теперь компонент <test> является полностью прозрачной оболочкой, что означает, что его можно использовать точно так же, как обычный элемент <div>: все слушатели будут работать без модификатора .native.

Демо:

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>

Используя метод $emit:

Мы также можем использовать для этой цели метод $emit, который помогает нам прослушивать события дочерних компонентов в родительском компоненте. Для этого нам сначала нужно создать настраиваемое событие из дочернего компонент вроде:

Test.vue (дочерний компонент)

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

Важно: всегда используйте kebab-case в названиях событий. Для получения дополнительной информации и демонстрации этого пункта, пожалуйста, ознакомьтесь с этим ответом: VueJS передает вычисленное значение от компонента к родителю.

Теперь нам просто нужно прослушать это созданное настраиваемое событие в родительском компоненте, например:

App.vue

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

Итак, в основном вместо v-on:click или сокращения @click мы будем просто использовать v-on:my-event или просто @my-event.

Демо:

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>


С Vue 3

Использование v-bind="$attrs":

Vue 3 во многом облегчит нашу жизнь. Один из примеров этого заключается в том, что это поможет нам создать более простую прозрачную оболочку с очень меньшим количеством настроек, просто используя v-bind="$attrs". Используя это в дочерних компонентах, не только наш слушатель будет работать напрямую с родительским, но и любой другой атрибут также будет работать так же, как и обычный <div>.

Итак, что касается этого вопроса, нам не нужно будет ничего обновлять в Vue 3, и ваш код по-прежнему будет работать нормально, поскольку <div> является корневым элементом здесь, и он будет автоматически прослушивать все дочерние события.

Демо №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>

Но для сложных компонентов с вложенными элементами, где нам нужно применить атрибуты и события к основному <input /> вместо родительской метки, мы можем просто использовать v-bind="$attrs"

Демо № 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>

person palaѕн    schedule 11.05.2020

Немного многословно, но вот как я это делаю:

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

ОБНОВЛЕНИЕ: пример добавлен @sparkyspider

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

В div-container компоненте ...

<template>
  <div @click="$emit('click', $event);">The inner div</div>
</template>
person Neps    schedule 22.06.2018
comment
куда это идет? Зачем ты его туда положил? Пожалуйста, расскажите подробнее о людях, просматривающих этот ответ. - person mix3d; 03.12.2018
comment
В этом примере это будет помещено в тег div в компоненте Test.vue. Затем вы можете использовать v-on: click = testFunction или @ click = testFunction при использовании компонентного теста в App.vue. - person Tim Wickstrom; 30.01.2019
comment
Я изменил его на $emit, но ничего не происходит. Надо ли что-то делать помимо $emit? jsfiddle.net/xwvhy6a3 - person Richard Barraclough; 15.03.2020
comment
@RichardBarraclough, ваш компонент теперь генерирует ваше настраиваемое событие clickTreeItem. Затем нужно обработать, что делать с этим событием при использовании этого компонента: v-on: myEvent = myMethod - person Neps; 16.03.2020

Собственные события компонентов недоступны напрямую из родительских элементов. Вместо этого вы должны попробовать v-on:click.native="testFunction", или вы также можете создать событие из Test компонента. Вроде v-on:click="$emit('click')".

person Community    schedule 04.06.2018

Из документации:

Из-за ограничений в JavaScript Vue не может обнаружить следующие изменения в массиве:

  1. Когда вы напрямую устанавливаете элемент с индексом, например vm.items [indexOfItem] = newValue
  2. Когда вы изменяете длину массива, например vm.items.length = newLength

В моем случае я столкнулся с этой проблемой при переходе с Angular на VUE. Исправить было довольно легко, но найти действительно сложно:

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