Vue v-on:click 在组件上不起作用

Vue v-on:click does not work on component

我正在尝试在组件内使用点击指令,但它似乎不起作用。当我单击组件时,当我应该在控制台中获得 'test clicked' 时,什么也没有发生。我在控制台中没有看到任何错误,所以我不知道我做错了什么。

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>

如果你想在组件的根元素上监听本地事件,你必须对 v-on 使用 .native 修饰符,如下所示:

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

或在shorthand中,按照评论中的建议,您也可以这样做:

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

Reference to read more about native event

我认为 $emit 功能更适合我认为您的要求。它使您的组件与 Vue 实例分离,以便它可以在许多上下文中重用。

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

在HTML

中使用
// Parent component
<test @test-click="testFunction">

无法直接从父元素访问组件的本机事件。相反,您应该尝试 v-on:click.native="testFunction",或者您也可以从 Test 组件发出事件。喜欢 v-on:click="$emit('click')".

有点冗长,但我是这样做的:

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

更新: @sparkyspider 添加的示例

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

div-container组件中...

<template>
  <div @click="$emit('click', $event);">The inner div</div>
</template>

这是 ,但有详细信息。


注意:如果您不想修改您的组件或无权访问它,更合适table。


为什么@click 不能正常工作?

组件很复杂。一个组件可以是一个小巧的精美按钮包装器,另一个组件可以是一个完整的 table,里面有一堆逻辑。当绑定 v-model 或使用 v-on 时,Vue 并不知道你到底期望什么,所以所有这些都应该由组件的创建者处理。

如何处理点击事件

根据 Vue docs$emit 将事件传递给父级。来自文档的示例:

主文件

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

组件

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

(@v-on shorthand)

组件处理本机 click 事件并发出父级的 @enlarge-text="..."

enlarge-text 可以替换为 click 以使其看起来像我们正在处理本机点击事件:

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

但这还不是全部。 $emit 允许通过事件传递特定值。在原生click的情况下,值为MouseEvent(与Vue无关的JS事件)

Vue 将该事件存储在 $event 变量中。因此,最好用事件发出 $event 以创建本机事件用法的印象:

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

正如 Chris Fritz 所说 (Vue.js Core Team Emeriti) in VueCONF US 2019

If we had Kia enter .native and then the root element of the base input changed from an input to a label suddenly this component is broken and it's not obvious and in fact, you might not even catch it right away unless you have a really good test. Instead by avoiding the use of the .native modifier which I currently consider an anti-pattern, and will be removed in Vue 3, you'll be able to explicitly define that the parent might care about which element listeners are added to...

使用 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方法来达到这个目的,它可以帮助我们在父组件中监听子组件的事件。为此,我们首先需要从子组件发出一个 custom event,例如:

Test.vue(子组件)

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

重要提示:始终使用 kebab-case 作为事件名称。有关这一点的更多信息和演示,请查看此答案:.

现在,我们只需要在父组件中监听这个发出的自定义事件,比如:

App.vue

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

所以基本上,我们将简单地使用 v-on:my-event@my-event.

而不是 v-on:click 或 shorthand @click

演示:

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>

来自documentation

由于 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
}

使用 @click.native 的一个用例是当您创建一个自定义组件并且您想要监听该自定义组件上的点击事件时。例如:

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

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

@click.native 总是适用于这种情况。

App.vue

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

Test.vue

<div @click="$emit('itemClicked', data)">
     click here
</div>