前言
这是一个最近碰到的一个很奇怪的问题
情况如下一个 div 下面有一个 el-radio, 然后 div 上面配置了 click 的回调为 handleClick
然后 但是点击 div 的时候, handleClick 触发了两次
然后 这里 来模拟一下, 并解决一下 这个问题
这里的知识主要是 设计到 label 和 表单元素 的联动
测试用例
包了多组 div 下面的多组 el-radio, 然后 这时候点击 选项1, 选项2, 选项3 会发现 handleClick 触发了两次
<template>
<div class="testParent" >
<div class="radioParent" v-for="item in selectList" @click="handleClick($event, item)" style="float: left; ">
<el-radio v-model="checkItem" :label="item.name"> {{item.name}} </el-radio>
</div>
</div>
</template>
<script>
export default {
name: 'App',
components: {
},
data() {
return {
checkItem: '',
selectList: [
{code: 'opt1', name: '选项1' },
{code: 'opt2', name: '选项2' },
{code: 'opt3', name: '选项3' }
]
};
},
computed: {},
created() {
},
methods: {
handleClick($event, item) {
console.log(' clicked item ', $event, item);
}
}
};
</script>
<style>
.radioParent {
cursor: pointer;
width: 80px;
height: 32px;
display: flex;
justify-content: center;
align-items: center;
background-color: #ffffff;
border-radius: 2px;
border: solid 1px #d9dadb;
color: #8c9094;
font-size: 16px;
margin-right: 13px;
}
.radioParent .el-radio {
height: 32px !important;
line-height: 32px !important;
}
.radioParent .el-radio__input {
height: 32px !important;
line-height: 32px !important;
}
</style>
点击一下 选项2 结果如下
可以看到 handleClick 的回调触发了两次
el-radio 中为什么点击 span 也能选中目标?
这是 我很疑惑的一个点, 然后 点开了 element-ui 的源码 编译了一下
然后 测试用例 复制进去, 跑起来 调试一下
然后 这里的 input 的 change 触发了, 但是 不知道 是哪里触发的
我这里点击的是 span[其内容是”选项2”], 但是事件传递到了 input 这里
数据视图切换的标准是 input 框的 change 触发, 造成了 model 的更新, 才会进行切换
然后开始 其他的调试
radioParent 的 div 的 事件处理列表 如下, 然后 其子元素也继承了这一套 事件处理列表
然后 我们把 radioParent 的 div 的 事件处理列表 删除掉
然后点击查看 radioParent 以及下面的各个子元素的 事件处理列表, 可以看到 除了 input框 还有 focus, blur, change 的 事件处理列表
input 上面的事件如下
然后 此时再从 “其他选项” 点击到 “选项3” 的时候, 可以看到 这个 click 还是可以正常响应
然后 这里可以判断出的就是 这个 input 的 change 事件 不是 span 传递过来的
label + input 标签的联动 – 修改 label
然后 最终搜索了一下 发现是 label + input 标签的联动
当点击了 label 标签范围内的数据, 会自动视为点击了 input 标签, 提升了用户体验
为了验证这个问题, 我们将 “选项3” 的 label 标签换成 span, 初始化状态 选中 “选项2”
点击 span “选项3” 可以看到的现象是 hanleClick 触发了一次, 但是 视图模型并没有切换过去, 说明 input框 没有选中, 但是在有 label 标签的时候点击了 label 进而使 input框选中了
这里实际点击的元素是 el-radio__label 的 span
点击 input 框, 可以看到的现象是 hanleClick 触发了一次, 但是 视图模型并没有切换过去
说明 input框 没有选中, 但是在有 label 标签的时候点击了 label 进而使 input框选中了
这里实际点击的元素是 el-radio__inner 的 span
上面的点击 span 的时候 input 框没有被选中的道理很好理解, 但是 后者呢? 为什么选中了 input 框, 这时候点击 还是没有触发 input 框的更新
这是因为 element-ui 配置的 z-index
input 框的 .el-radio__original 设置的 z-index 是 -1, el-radio__inner 的 span 的 z-index 是 auto, 在外层父元素的 z-index 也是 auto, 所以 点击到 按钮位置 实际上点击到的是 span
这个也可以通过 上面的鼠标事件的 target 来进行判断
“选项3” 的 input 设置一个 z-index 1000, 然后 点击看一下, 这之后 点击 input 元素 就能够正常选中了
看一下 事件点击的元素 就是目标 input 框了
label + input 标签的联动 – 修改 input
修改 el-radio 的代码, 将 input 修改为 span, 然后 我们来查看一下 效果
点击一个选择框, 可以看到 handleClick 只会触发一次了
同时 也因为缺少了 input 框, 导致没有触发 input 的 change, 导致 checkItem 未产生变化
问题的调试
上面的这一个章节 只是一个基础的补充
然后 我们这里来看一下 我们这里用例的问题, 为什么 点击了一次 触发了 两次 click 函数
第一个事件触发是点击的元素, 比如我们这里点击 label, 那就是 el-radio__label 的 span 元素, 如果我们这里点击的是 input 的位置, 那就是 el-radio__inner 的 span
这里我们点击目标元素, 目标元素
然后第二次事件触发的是 input 元素, 可以发现 不管点击的是 div 的哪一个区域, 第二次的 target 都是 input 元素
这个 通过上面一个章节 的基础的补充, 应该还是 能够比较快的猜到是 label 的点击 然后使得事件传递到了 input 框
问题的解决
- 这时候, 在这个场景下面 通常来说的处理方式 就是, 拿到 事件event, 然后根据 target 进行一个过滤, 比如 只处理 input 的这一个事件, 来保证业务的准确
- 注册事件的时候注册为 click.prevent, 这样会阻止事件的默认行为, 比如这里的点击了 label 使得 input 收到同样的事件, 所以只会有第一个点击目标元素的事件
- 不使用 el-radio, 这个实现是固定的, 无法改变, 使用其他的替代方案