elementUI中点击触发两次点击事件原理及解决方法
最近在研究vue3,在学习vue3和element-plus
的过程中,突然发现el-radio
的一个问题,element的版本号如下
"element-plus": "2.2.27",
代码如下:
<template>
<div class="button-group">
<el-radio-group v-model="buttonGroup">
<el-radio-button label="本周热点" @click="handleBtnGroup('本周热点', $event)"/>
<el-radio-button label="专题分析" @click="handleBtnGroup('专题分析', $event)"/>
<el-radio-button label="指标收藏" @click="handleBtnGroup('指标收藏', $event)"/>
<el-radio-button label="历史记录" @click="handleBtnGroup('历史记录', $event)"/>
</el-radio-group>
</div>
</template>
<script setup name="chatbiIndex">
//按钮组当前按钮选择
let buttonGroup = ref('')
//控制单选框方法
function handleBtnGroup(btnName,e){
if(buttonGroup.value == btnName){
buttonGroup.value = ""
} else {
buttonGroup.value = btnName
}
}
</script>
这段代码很好理解,本意是控制单选框点击一下选中,如果第二次点击同一个单选框则取消选择,但是在执行的时候发现点击之后单选框并不会被选中,buttonGroup
的值依然为空字符串,使用console.log(e)
打印后发现结果如下:
很明显函数被执行两次,根据逻辑如果第二次触发的是同一个按钮则会取消选择,那么问题来了,明明我只是点击了一次,为什么函数会被执行两次呢。
于是我先查看element-plus中radio-button.vue
文件的部分源码,源码如下
<template>
<label
:class="[
ns.b('button'),
ns.is('active', modelValue === actualValue),
ns.is('disabled', disabled),
ns.is('focus', focus),
ns.bm('button', size),
]"
>
<input
ref="radioRef"
v-model="modelValue"
:class="ns.be('button', 'original-radio')"
:value="actualValue"
type="radio"
:name="name || radioGroup?.name"
:disabled="disabled"
@focus="focus = true"
@blur="focus = false"
/>
<span
:class="ns.be('button', 'inner')"
:style="modelValue === actualValue ? activeStyle : {}"
@keydown.stop
>
<slot>
{{ label }}
</slot>
</span>
</label>
</template>
从源码中可以看出来,这个组件内部由input和span组成,根元素是一个和input联动的label,然后再根据我观察的触发两次的元素来看,span是正常点击的,而input则可能是自动触发的,打开input打印的click结果就可以看出来:
这一切看起来很复杂,但是只要慢慢理清思路就很好理解。
首先我们将@click事件绑定在了el-radio-button
这个组件上,因为vue3和vue2不同,vue3是默认原生事件,如果在子组件内使用defineEmits
定义了自定义事件,才会转为自定义事件,很显然element并没有定义,所以我们走的原生事件。
那么这个click的原生事件就会绑定在el-radio-button
组件的根元素也就是label上,然后因为事件冒泡机制,当label里面的input或者span元素被点击时,也会触发这个click事件。
关键的来了,因为input在源码中绑定了v-model
,如果看过vue
的文档就会知道,v-model
的实现主要是靠v-bind
和v-on
两个指令共同实现:
所以我们合理推断,<input type=radio>
元素中的v-model
肯定是:value
加上@click
。
我们都知道,被label包裹的<input type=radio>
元素,如果label被点击,则视为input被点击,而又因为input的click事件被label冒泡的click事件代替了,导致在点击span或者label(其实都是触发的label身上的点击事件)时,input自身也会执行一次click事件,因而触发了两次我们写的click对应的handleBtnGroup
函数。
当然,上述都是我的猜想,事实是否真的是这样,我们写个简单的代码段就明白了。
<label @click="test">测试
<input type="radio" v-model="buttonGroup" value="测试" name="一号"> <span>二号</span>
</label>
......
......
function test(e){
console.log(e)
}
页面如下:
这是点击测试所打印的结果
这是点击二号所打印的结果
这是点击一号,也就是input元素打印的结果
以上试验说明input函数中的click确实被冒泡事件替换,而且点击label或者span确实会导致input自身自动触发一次click,我们的猜想是正确的.
那么该如何解决这一切呢,其实很简单,因为input的click事件改变的原因是冒泡导致的,那么只要input不被冒泡影响就可以了,改动代码如下:
<label @click="test">测试 <input type="radio" v-model="buttonGroup" @click.stop value="测试" name="一号"> <span>二号</span> </label>
阻止冒泡确实有效,具体打印结果我不再截图,有兴趣的朋友可以自己试试。
其实官方在新版本中也已经修复了该bug,2.3.10版本及以上没有该bug。
那么问题来了,有些朋友比如我,使用的是2.3.10版本以下的该如何解决呢,网上也有一些办法,比如在触发click的时候判断点击的target是哪个元素,但是都比较麻烦,其实我们完全可以从源头入手,在最开始写给el-radio-button
组件的click后面加上prevent修饰符:
<el-radio-group v-model="buttonGroup">
<el-radio-button label="本周热点" @click.prevent="handleBtnGroup('本周热点', $event)"/>
<el-radio-button label="专题分析" @click.prevent="handleBtnGroup('专题分析', $event)"/>
<el-radio-button label="指标收藏" @click.prevent="handleBtnGroup('指标收藏', $event)"/>
<el-radio-button label="历史记录" @click.prevent="handleBtnGroup('历史记录', $event)"/>
</el-radio-group>
原理是阻止input触发自己的默认事件,也就是label被点击时算作input被点击。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南