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-bindv-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被点击。

posted @   花粉回家  阅读(2517)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示