前言

这是一个最近碰到的一个很奇怪的问题 

情况如下一个 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 的回调触发了两次

48 div 下面包含 el-radio, 点击 div 事件被触发多次_vue.js

 

 

el-radio 中为什么点击 span 也能选中目标?

这是 我很疑惑的一个点, 然后 点开了 element-ui 的源码 编译了一下

然后 测试用例 复制进去, 跑起来 调试一下

然后 这里的 input 的 change 触发了, 但是 不知道 是哪里触发的

我这里点击的是 span[其内容是”选项2”], 但是事件传递到了 input 这里 

数据视图切换的标准是 input 框的 change 触发, 造成了 model 的更新, 才会进行切换 

48 div 下面包含 el-radio, 点击 div 事件被触发多次_radio_02

 

然后开始 其他的调试

radioParent 的 div 的 事件处理列表 如下, 然后 其子元素也继承了这一套 事件处理列表

48 div 下面包含 el-radio, 点击 div 事件被触发多次_测试用例_03

 

然后 我们把 radioParent 的 div 的 事件处理列表 删除掉 

然后点击查看 radioParent 以及下面的各个子元素的 事件处理列表, 可以看到 除了 input框 还有 focus, blur, change 的 事件处理列表

48 div 下面包含 el-radio, 点击 div 事件被触发多次_radio_04

 

input 上面的事件如下 

48 div 下面包含 el-radio, 点击 div 事件被触发多次_vue.js_05

 

然后 此时再从 “其他选项” 点击到 “选项3” 的时候, 可以看到 这个 click 还是可以正常响应 

然后 这里可以判断出的就是 这个 input 的 change 事件 不是 span 传递过来的 

48 div 下面包含 el-radio, 点击 div 事件被触发多次_javascript_06

 

 

label + input 标签的联动 – 修改 label

然后 最终搜索了一下 发现是 label + input 标签的联动 

当点击了 label 标签范围内的数据, 会自动视为点击了 input 标签, 提升了用户体验 

为了验证这个问题, 我们将 “选项3” 的 label 标签换成 span, 初始化状态 选中 “选项2”

 

点击 span “选项3” 可以看到的现象是 hanleClick 触发了一次, 但是 视图模型并没有切换过去, 说明 input框 没有选中, 但是在有 label 标签的时候点击了 label 进而使 input框选中了

48 div 下面包含 el-radio, 点击 div 事件被触发多次_测试用例_07

 

这里实际点击的元素是 el-radio__label 的 span

48 div 下面包含 el-radio, 点击 div 事件被触发多次_radio_08

 

点击 input 框, 可以看到的现象是 hanleClick 触发了一次, 但是 视图模型并没有切换过去 

说明 input框 没有选中, 但是在有 label 标签的时候点击了 label 进而使 input框选中了

48 div 下面包含 el-radio, 点击 div 事件被触发多次_vue.js_09

 

这里实际点击的元素是 el-radio__inner 的 span

48 div 下面包含 el-radio, 点击 div 事件被触发多次_javascript_10

 

上面的点击 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 来进行判断 

48 div 下面包含 el-radio, 点击 div 事件被触发多次_radio_11

 

“选项3” 的 input 设置一个 z-index 1000, 然后 点击看一下, 这之后 点击 input 元素 就能够正常选中了

48 div 下面包含 el-radio, 点击 div 事件被触发多次_radio_12

 

看一下 事件点击的元素 就是目标 input 框了 

48 div 下面包含 el-radio, 点击 div 事件被触发多次_事件处理_13

 

 

label + input 标签的联动 – 修改 input

修改 el-radio 的代码, 将 input 修改为 span, 然后 我们来查看一下 效果

48 div 下面包含 el-radio, 点击 div 事件被触发多次_javascript_14

点击一个选择框, 可以看到 handleClick 只会触发一次了 

同时 也因为缺少了 input 框, 导致没有触发 input 的 change, 导致 checkItem 未产生变化 

48 div 下面包含 el-radio, 点击 div 事件被触发多次_测试用例_15

 

 

问题的调试

上面的这一个章节 只是一个基础的补充

然后 我们这里来看一下 我们这里用例的问题, 为什么 点击了一次 触发了 两次 click 函数 

第一个事件触发是点击的元素, 比如我们这里点击 label, 那就是 el-radio__label 的 span 元素, 如果我们这里点击的是 input 的位置, 那就是 el-radio__inner 的 span 

这里我们点击目标元素, 目标元素

48 div 下面包含 el-radio, 点击 div 事件被触发多次_javascript_16

 

然后第二次事件触发的是 input 元素, 可以发现 不管点击的是 div 的哪一个区域, 第二次的 target 都是 input 元素 

这个 通过上面一个章节 的基础的补充, 应该还是 能够比较快的猜到是 label 的点击 然后使得事件传递到了 input 框 

48 div 下面包含 el-radio, 点击 div 事件被触发多次_javascript_17

 

 

问题的解决

  1. 这时候, 在这个场景下面 通常来说的处理方式 就是, 拿到 事件event, 然后根据 target 进行一个过滤, 比如 只处理 input 的这一个事件, 来保证业务的准确 
  2. 注册事件的时候注册为 click.prevent, 这样会阻止事件的默认行为, 比如这里的点击了 label 使得 input 收到同样的事件, 所以只会有第一个点击目标元素的事件 
  3. 不使用 el-radio, 这个实现是固定的, 无法改变, 使用其他的替代方案