概述
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的时候没有 响应是正常的。
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事件,导致事件再次冒泡。
解决办法:
方法有很中拉,
- 2次响应由 label造成,那我们不要label了,这对于使用iview的情况下是不可能的,因为iview就是使用 label 和 input 做的
- 响应函数里判断event的 tagName ,如果 tagName 为 label则不做处理,这中方法问题很多,要做 event的浏览器兼容,还有判断label有没有关联 input等,稍显复杂
- 根据通过事件触发的时间戳来判断,相隔太近则认为是一次点击。
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 可以取消了。
遇到问题欢迎评论提问,不足之处也欢迎指正。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。