乐闻世界logo
搜索文章和话题

Why v- on :click does not work on a vue component?

8 个月前提问
4 个月前修改
浏览次数81

6个答案

1
2
3
4
5
6

在 Vue.js 中,v-on 指令用于监听 DOM 事件,例如用户的点击事件。当您在原生 HTML 元素上使用 v-on:click 或者是缩写形式 @click,它会正常工作,因为这会在该元素上绑定一个点击事件监听器。但是,当您将同样的指令用在一个 Vue 组件上时,情况就有所不同。

组件上的 v-on 监听器并不是直接监听子组件根元素的原生事件,而是监听由子组件触发的自定义事件。Vue 组件实例不会自动将它们的事件监听器作为原生 DOM 事件处理程序。这是因为组件的根元素可以是任何元素或者其他组件,Vue 不会对它做特殊处理。

如果你要在组件上监听一个原生事件(比如点击事件),你需要使用 .native 修饰符,指示 v-on 监听原生事件,如下所示:

vue
<!-- MyComponent.vue --> <template> <button @click="$emit('click')">Click me</button> </template> <script> export default { emits: ['click'] }; </script>
vue
<!-- ParentComponent.vue --> <template> <my-component @click.native="handleClick"></my-component> </template> <script> export default { methods: { handleClick() { console.log('Button clicked!'); } } }; </script>

在这个例子中,我们有一个子组件 MyComponent,它在一个按钮上监听点击事件,并在点击时触发一个名为 click 的自定义事件。在父组件中,我们使用 .native 修饰符来监听这个原生的点击事件。但是要注意的是,从 Vue 3 开始,.native 修饰符已被移除,因为 Vue 3 提倡组件应该显式地定义和触发它们自己的自定义事件。

因此,在 Vue 3 中,您应该在子组件中通过 $emit 来显式地发出自定义事件,并在父组件中监听这些事件而不是原生事件。如果你确实需要在父组件中监听子组件根元素的原生事件,你应该在子组件内部绑定一个原生事件监听器,并在需要时触发一个自定义事件。

2024年6月29日 12:07 回复

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

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

或者简而言之,正如评论中所建议的,您也可以这样做:

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

参考阅读有关本机事件的更多信息

2024年6月29日 12:07 回复

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

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

在 HTML 中使用它:

shell
<!-- Parent component --> <test @test-click="testFunction">
2024年6月29日 12:07 回复

这是@Neps 的答案,但有详细信息。


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


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

组件很复杂。一个组件可以是一个小型的精美按钮包装器,另一个组件可以是内部有一堆逻辑的整个表格。Vue 不知道绑定v-model或使用时您到底期望什么v-on,因此所有这些都应该由组件的创建者处理。

如何处理点击事件

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

主文件

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

成分

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

@v-on 简写

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

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

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

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

Vue 将该事件存储在$event变量中。因此,最好$event与事件一起发出,以营造本机事件使用的印象:

shell
<button v-on:click="$emit('click', $event)"> Enlarge text </button>
2024年6月29日 12:07 回复

正如 Chris Fritz(Vue.js核心团队荣誉退休人员)在VueCONF US 2019中提到的

如果我们让 Kia 输入.native,然后基本输入的根元素从输入更改为标签,突然该组件被破坏并且不明显,事实上,除非您有一个非常好的测试,否则您甚至可能无法立即捕获它。相反,通过避免使用我目前认为是反模式并将在 Vue 3 中删除的.native修饰符,您将能够显式定义父级可能关心添加到哪些元素侦听器...

使用 Vue 2

使用$listeners

因此,如果您使用 Vue 2,解决此问题的更好选择是使用完全透明的包装器逻辑。为此,Vue 提供了一个$listeners属性,其中包含组件上使用的侦听器对象。例如:

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

然后我们只需要添加v-on="$listeners"到组件中test,例如:

Test.vue(子组件)

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

现在该<test>组件是一个完全透明的包装器,这意味着它可以像普通元素一样使用<div>:所有侦听器都可以工作,无需.native修饰符。

演示:

shell
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>

运行代码片段Hide results

展开片段

使用$emit方法:

我们也可以使用该$emit方法来实现此目的,这有助于我们在父组件中监听子组件的事件。为此,我们首先需要从子组件发出自定义事件,例如:

Test.vue(子组件)

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

**重要提示:**始终使用短横线命名的事件名称。有关这一点的更多信息和演示,请查看此答案:VueJS将计算值从组件传递到父级

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

应用程序.vue

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

所以基本上,我们将简单地使用or 来代替or 的v-on:click简写。@click``v-on:my-event``@my-event

演示:

shell
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>

运行代码片段Hide results

展开片段


使用 Vue 3

使用v-bind="$attrs"

Vue 3 将从很多方面让我们的生活变得更加轻松。一个例子是,它将帮助我们创建一个更简单的透明包装器,配置更少,只需使用v-bind="$attrs". 通过在子组件上使用它,我们的侦听器不仅可以直接从父组件工作,而且任何其他属性也将像普通的<div>.

因此,对于这个问题,我们不需要更新 Vue 3 中的任何内容,您的代码仍然可以正常工作,就像<div>这里的根元素一样,它将自动侦听所有子事件。

演示#1:

shell
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="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.8/vue.global.min.js"></script> <div id="myApp"> <test v-on:click="testFunction"></test> </div>

运行代码片段Hide results

展开片段

但是,对于具有嵌套元素的复杂组件,我们需要将属性和事件应用于而<input />不是父标签,我们可以简单地使用v-bind="$attrs"

演示#2:

shell
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="https://cdnjs.cloudflare.com/ajax/libs/vue/3.4.8/vue.global.min.js"></script> <div id="myApp"> <base-input label="Search: " placeholder="Search" @keyup="search"> </base-input><br/> </div>

运行代码片段Hide results

展开片段

2024年6月29日 12:07 回复

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

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

更新: @sparkyspider 添加的示例

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

div-container组件...

shell
<template> <div @click="$emit('click', $event);">The inner div</div> </template>
2024年6月29日 12:07 回复

你的答案