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>

9 ответов

Решение

Если вы хотите прослушать собственное событие в корневом элементе компонента, вы должны использовать модификатор .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>

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

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

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

<test @test-click="testFunction">

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


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


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

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

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

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

Основной файл

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

Составная часть

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

(@ это v-on стенография)

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

enlarge-text можно заменить на click чтобы это выглядело так, как будто мы обрабатываем собственное событие 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>

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

если бы у нас была Киа .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> element: все слушатели будут работать без .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>

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

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

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

Один из вариантов использования @click.native - это когда вы создаете настраиваемый компонент и хотите прослушивать событие щелчка на настраиваемом компоненте. Например:

      #CustomComponent.vue
<div>
  <span>This is a custom component</span>
</div>

#App.vue
<custom-component @click.native="onClick"></custom-component>

@click.native всегда работайте в этой ситуации.

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

Из-за ограничений 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
}

App.vue

      <div id="app">
    <test @itemClicked="testFunction($event)"/>
</div>

Test.vue

      <div @click="$emit('itemClicked', data)">
     click here
</div>
Другие вопросы по тегам