记一次使iview库的Radio可取消的过程

0

概述

ui库用的是iview .
radio、radioGroup是我们非常常用的组件。radio有一个特征是选中之后无法取消。现实中取消radio的需求是常见且可以理解的。
所以看到这个需求之后第一尝试 在iview组件之上搞一搞,这一搞就入坑了,现在就来理一理我的入坑之路吧。

1. 原生组件radio的取消

首先我们来看一下在 vue 中使用原生的radio组件如何使用取消。 原生radio取消选中的文章非常多,随便拎一篇看看就行,比如 取消radio的三种方法
嗯,原理就是给radio元素的checked属性赋值为 false。
具体在 vue 中使用是这样的:

const { name, value } = data;
<div>
    <input
        class="xxx"
        type="radio"
        name={this.id}
        ref={name}
        value={value}
        onClick={(vlaue) => this.radioGroupChange(value, name)}
    />
    {name}
</div>;

// 方法
radioGroupChange (value, name) {
    if (this.checked === value) {
        // 取消选中
        this.$refs[name].checked = false;
        // 当前选中值为空
        this.checked = '';
    } else {
        this.checked = value;
    }
}

这样就OK了。大概唯一区别是 在 vue中通过 ref取到真实 dom。

2. iview中radio取消之路

借鉴原生的radio,看来取消也不难嘛,监听radio或者 radio的change事件嘛。然而你会发现,重复点选某一个 radio的时候,iview中的on-change函数跟本木有响应。这是为什么呢? 去 iview 源码中看一看

2.1 iview组件radio源码探究

先上代码为敬:
先看下 radio.vue的 template

<template>
    <label :class="wrapClasses">
        <span :class="radioClasses">
            <span :class="innerClasses"></span>
            <input
                type="radio"
                :class="inputClasses"
                :disabled="disabled"
                :checked="currentValue"
                :name="groupName"
                @change="change"
                @focus="onFocus"
                @blur="onBlur">
        </span><slot>{{ label }}</slot>
    </label>
</template>

再看下 change的响应函数

 change (event) {
    // debugger
    if (this.disabled) {
        return false;
    }

    const checked = event.target.checked;
    this.currentValue = checked;

    const value = checked ? this.trueValue : this.falseValue;
    this.$emit('input', value);

    if (this.group) {
        if (this.label !== undefined) {
            this.parent.change({
                value: this.label,
                checked: this.value
            });
        }
    } else {
        this.$emit('on-change', value);
        this.dispatch('FormItem', 'on-form-change', value);
    }
},

一开始怀疑 change事件中对 value做了处理,只有 不一样的时候才


emit,仔细看,change函数中并没有这部分处理。
问题在于 input 这里监听的 change 事件而不是 click事件,所以反复点击同一个 radio的时候没有 响应是正常的。
clipboard.png

2.2 如何监听到 click事件

看到上面是不是就想 我在 radio上绑定一个click 事件就可以了;事实证明是不可以的,想一下就知道了,因为 iview并没有监听click事件自然也没有把click事件 emit到父组件,所以是无法监听click的。
既然 Radio 不支持click,那就想办法在能让元素响应 click

2.2.1 Radio外增加一层 元素: div/span

这里的思路是在 RadioGroup的Radio 外包裹一层 div/span,在这个元素上绑定 click 函数,这个元素肯定会响应 click,在响应函数中判断 当前 选中的 value(这个变量通常会维护在data中,暂且用 this.value 表示) 和点击元素包裹的 value 值是否相同,相同则将 this.value 置为空,则可以达到取消的效果。
经过实践,该方法是可行的。 值得注意的是这种情况下需要取消RadioGroup的on-change事件监听,否则radio改变了this.value,会触发RadioGroup的on-change事件,导致无法取消。 还有一个弊端是 需要给 Radio增加一个包裹元素,可能还需要对元素写个样式,不要影响到原Radio 元素布局, 那么还有没有更好的方法呢?

2.2.2 Radio更好的绑定click的方式

vue中提供了监听原生事件的修饰符,在jsx形式的vue监听原生事件的写法如下:

<Radio
    nativeOnClick={this.handleRadioClick.bind(this, value)}
    label={value}
    disabled={disabled}
    >
    <span>{label}</span>
</Radio>

所以我们不用增加元素也能监听到click事件,接下里就可以在响应函数中 处理取消/选中的逻辑了。

2.2.3 Radio 怎么响应了2次?

嗯, 终于找到一个很好的绑定 click的方法,然而测试的时候,却发现 点击一次却响应了 两次点击函数,百思不得其解吗? 这时候 baidu 或者google给了我们答案: 一个文章说的特别好,解释了两次的原因:
如下文章链接:
触发的事件源分别为input和label;

触发条件很简单:

1、监听的是label和input的上层元素click事件

2、label和input关联(for或者input在label下)

问题原因::

点击label的时候,事件冒泡一次,同时会触发关联的input的click事件,导致事件再次冒泡。

解决办法:
方法有很中拉,

  1. 2次响应由 label造成,那我们不要label了,这对于使用iview的情况下是不可能的,因为iview就是使用 label 和 input 做的
  2. 响应函数里判断event的 tagName ,如果 tagName 为 label则不做处理,这中方法问题很多,要做 event的浏览器兼容,还有判断label有没有关联 input等,稍显复杂
  3. 根据通过事件触发的时间戳来判断,相隔太近则认为是一次点击。

2.3 Radio/RadioGroup 可取消完整方案

有了上面的分析,下面我们完整是总结一下 让radio可以取消的步骤:
注意: 我下面写的是 vue的jsx 形式,所以如果是vue形式,请自行修改。
首先: template

 <RadioGroup
    onOn-change={this.sync.bind(this)}
    value={this.value}
    type={this.mode}
    size={this.size}
    style={this.css}
    class={this.theme}
>
    <Radio
        nativeOnClick={this.handleRadioClick.bind(this, value)}
        label={value}
        disabled={disabled}
    >
        <span>{label}</span>
    </Radio>
    <Radio
        nativeOnClick={this.handleRadioClick.bind(this, value)}
        label={value}
        disabled={disabled}
    >
        <span>{label}</span>
    </Radio>
</RadioGroup>

data 和 methods:

data() {
    return {
        value: ''
    }
}
methods: {
    handleRadioClick (value) {
        let now = +new Date();
        if (now- this.evTimeStamp < 100) {
            return;
        }
        this.evTimeStamp = now;
        value = this.value === value ? '' : value;
        this.update(value);  // 可以理解为vuex 通知更新  this.value
    },
}

限于本身实现还有其他关联部分,这里没有加上 更新 this.value具体代码,但是我相信看到这里应该已经知在使用 ui 库的情况如何 是 radio 可以取消了。

遇到问题欢迎评论提问,不足之处也欢迎指正。

你可能感兴趣的

载入中...