深入Vue之解析ElementUI组件--radio
2019-10-08 本文已影响0人
Lia代码猪崽
一、github地址
https://github.com/ElemeFE/element/blob/dev/packages/radio/src/radio.vue
二、文档地址
https://element.eleme.cn/#/zh-CN/component/radio
三、解析过程(很多注释在源代码是不合法的,这里只是为了更直观的展示)
<template>
<label
class="el-radio"
:class="[ // 注解1
border && radioSize ? 'el-radio--' + radioSize : '',
{ 'is-disabled': isDisabled },
{ 'is-focus': focus },
{ 'is-bordered': border },
{ 'is-checked': model === label }
]"
role="radio" // 注解2
:aria-checked="model === label" // 注解2
:aria-disabled="isDisabled" // 注解2
:tabindex="tabIndex" // 注解2
@keydown.space.stop.prevent="model = isDisabled ? model : label" // 注解3
>
<span class="el-radio__input" // 注解4
:class="{
'is-disabled': isDisabled,
'is-checked': model === label
}"
>
<span class="el-radio__inner"></span>
<input
ref="radio"
class="el-radio__original"
:value="label"
type="radio"
aria-hidden="true"
v-model="model" // 注解1.5
@focus="focus = true"
@blur="focus = false"
@change="handleChange" // 注解5
:name="name"
:disabled="isDisabled"
tabindex="-1"
>
</span>
<span class="el-radio__label" @keydown.stop> // 注解6
<slot></slot>
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
<script>
import Emitter from 'element-ui/src/mixins/emitter';
export default {
name: 'ElRadio',
mixins: [Emitter],
inject: {
elForm: {
default: ''
},
elFormItem: {
default: ''
}
},
componentName: 'ElRadio',
props: {
value: {},
label: {},
disabled: Boolean,
name: String,
border: Boolean,
size: String
},
data() {
return {
focus: false
};
},
computed: {
isGroup() { // 注解1.1
let parent = this.$parent;
while (parent) {
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent;
} else {
this._radioGroup = parent;
return true;
}
}
return false;
},
model: { // 注解1.5
get() {
return this.isGroup ? this._radioGroup.value : this.value;
},
set(val) {
if (this.isGroup) {
this.dispatch('ElRadioGroup', 'input', [val]);
} else {
this.$emit('input', val);
}
this.$refs.radio && (this.$refs.radio.checked = this.model === this.label);
}
},
_elFormItemSize() { // 注解1.1
return (this.elFormItem || {}).elFormItemSize;
},
radioSize() { // 注解1.1
const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
return this.isGroup
? this._radioGroup.radioGroupSize || temRadioSize
: temRadioSize;
},
isDisabled() { // 注解1.2
return this.isGroup
? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
: this.disabled || (this.elForm || {}).disabled;
},
tabIndex() { // 注解2
return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
}
},
methods: {
handleChange() { // 注解5
this.$nextTick(() => {
this.$emit('change', this.model); // 注解1.5
this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
});
}
}
};
</script>
1. 动态class样式
- 调用组件时候有没有传来
border
,且radioSize
的返回值是否为true
,则有类'el-radio--' + radioSize
// 有this.elFormItem,则返回this.elFormItem.elFormItemSize;否则返回{}.elFormItemSize,即返回undefined
_elFormItemSize() {
return (this.elFormItem || {}).elFormItemSize;
},
// 判断是否为radio-group
isGroup() {
let parent = this.$parent; // 获取父节点的DOM元素
while (parent) {
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent; // 改变parent,退出while循环,然后返回false
} else {
this._radioGroup = parent;
return true; // 已找到radio-group父节点,赋值给了this._radioGroup,返回true,代表是在group里
}
}
return false;
},
radioSize() {
const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size; // 获取到size
return this.isGroup
? this._radioGroup.radioGroupSize || temRadioSize
: temRadioSize; // 如果是在group里,则返回this._radioGroup.radioGroupSize || temRadioSize;否则返回temRadioSize
},
- 判断
isDisabled
返回的值,为true
则有类is-disabled
{ 'is-disabled': isDisabled },
// 是否为radio-group,是则返回this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled;否则返回this.disabled || (this.elForm || {}).disabled
isDisabled() {
return this.isGroup
? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
: this.disabled || (this.elForm || {}).disabled;
},
-
focus
是否为true,则有类is-focus
{ 'is-focus': focus },
- 调用组件时候有传来
border
,就有类is-bordered
{ 'is-bordered': border },
-
model
(下面会详讲)是否与label
(调用时父组件传来的label值)相等,就有类is-checked
。
{ 'is-checked': model === label }
model具体,有用到知识点computed的读取和设置:
model: {
get() {
return this.isGroup ? this._radioGroup.value : this.value; // 获取:是否在radio-group里,是返回this._radioGroup.value;否则返回this.value
},
set(val) { // 更新
if (this.isGroup) { // 如果在radio-group里,
this.dispatch('ElRadioGroup', 'input', [val]); // 下面详讲
} else {
this.$emit('input', val); // 如果不是,则直接触发调用父组件input事件,传递val过去
}
this.$refs.radio && (this.$refs.radio.checked = this.model === this.label);
}
},
Vue组件通信 dispatch,查找所有父级,直到找到要找到的父组件,并在身上触发指定的事件。
// dispatch(componentName, eventName, params) {}
// @param { componentName } 组件名称
// @param { eventName } 事件名
// @param { params } 参数
this.dispatch('ElRadioGroup', 'input', [val]);
2. HTML5中的aria与role
这些都是HTML5针对html tag增加的属性,一般是为不方便的人士提供的功能,比如屏幕阅读器。
role
的作用是描述一个非标准的tag的实际作用。比如用div
做button
,那么设置div
的role="button"
,辅助工具就可以认出这实际上是个button
。
aria
的意思是Accessible Rich Internet Application
,aria-*
的作用就是描述这个tag在可视化的情境中的具体信息。
role="radio" // 这实际上是个单选radio
:aria-checked="model === label" // 当前是否被选中
:aria-disabled="isDisabled" // 当前是否被禁用
2.aria-label只有加在可被tab到的元素上,读屏才会读出其中的内容。可令tabindex
为0
可读,-1
不可读:
:tabindex="tabIndex"
// 如果是禁用状态,返回 -1
// 如果是在单选框组里(radio-group),且不是单选选中的,返回-1
// 其余返回0
tabIndex() {
return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
}
3. @keydown.space.stop.prevent
-
@keydown.space
:键修饰符.键别名,即按下了键盘的空格键触发事件 -
.stop
:停止冒泡 -
.prevent
:阻止默认行为
// 当按下键盘的空格键时,如果是禁用状态,不改变model的值;否则model为label的值;
// 停止冒泡、阻止默认行为
@keydown.space.stop.prevent="model = isDisabled ? model : label"
4. 单选框圆点
这一部分的全部HTML代码如下,待会儿会进行拆解:
<span class="el-radio__input"
:class="{
'is-disabled': isDisabled,
'is-checked': model === label
}"
>
<span class="el-radio__inner"></span>
<input
ref="radio"
class="el-radio__original"
:value="label"
type="radio"
aria-hidden="true"
v-model="model" // 注解1.5
@focus="focus = true"
@blur="focus = false"
@change="handleChange"
:name="name"
:disabled="isDisabled"
tabindex="-1"
>
</span>
![](https://img.haomeiwen.com/i7016617/08a0028ce0e703ea.png)
![](https://img.haomeiwen.com/i7016617/ae88668fb3b8d5f2.png)
- 外层,定位居中,判断是否为禁用和选中
<span class="el-radio__input"
:class="{
'is-disabled': isDisabled, // 如果为禁用状态则有'is-disabled'类样式
'is-checked': model === label // 如果为当前选中则有'is-checked'类样式
}"
>
...
</span>
.el-radio__input {
white-space: nowrap;
cursor: pointer;
outline: none;
display: inline-block;
line-height: 1;
position: relative;
vertical-align: middle;
}
- 内层1,实际上显示的样式
<span class="el-radio__inner"></span>
.el-radio__inner {
border: 1px solid #dcdfe6;
border-radius: 100%;
width: 14px;
height: 14px;
background-color: #fff;
position: relative;
cursor: pointer;
display: inline-block;
box-sizing: border-box;
}
.el-radio__inner:after {
width: 4px;
height: 4px;
border-radius: 100%;
background-color: #fff;
content: "";
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%,-50%) scale(0);
transition: transform .15s ease-in;
}
/*禁用样式*/
.el-radio__input.is-disabled .el-radio__inner {
background-color: #f5f7fa;
border-color: #e4e7ed;
cursor: not-allowed;
}
/*选中样式*/
.el-radio__input.is-checked .el-radio__inner {
border-color: #409eff;
background: #409eff;
}
.el-radio__input.is-checked .el-radio__inner:after {
transform: translate(-50%,-50%) scale(1);
}
- 内层2,因为太丑被隐藏了,但是实际上有大大的作用
<input
ref="radio" // 可通过this.$refs.radio获取到dom节点
class="el-radio__original" // 样式
:value="label" // 绑定值为父组件调用传来的label
type="radio" // 单选
aria-hidden="true" // 阅读器模式下隐藏
v-model="model" // 注解1.5
@focus="focus = true"
@blur="focus = false"
@change="handleChange" // 注解5
:name="name" // 绑定值为父组件调用传来的name
:disabled="isDisabled" // 根据isDisabled的值来定义是否禁用
tabindex="-1" // 阅读器模式下隐藏
>
.el-radio__original {
opacity: 0; // 透明度为0,依旧要占个位
outline: none;
position: absolute;
z-index: -1;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
}
5. $nextTick
handleChange() {
this.$nextTick(() => {
this.$emit('change', this.model); // 触发父组件调用change方法,参数为this.model(注解1.5)的值
this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model); // 如果是在单选框组里,调用this.dispatch('ElRadioGroup', 'handleChange', this.model)
});
}
6. label
<span class="el-radio__label" @keydown.stop> // 注解6
<slot></slot> // 提供插槽
<template v-if="!$slots.default">{{label}}</template> // 如果没有使用default插槽,显示调用时传进来的label值
</span>