一、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'; // 注解7
export default {
name: 'ElRadio',
mixins: [Emitter], // 注解7
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]);
-
this.$emit('input', val);寫法,就可以讓父組件
<el-radio v-model="radio" label="1">備選項</el-radio>"
里的radio
自動更新,實現父子組件間傳值。
<el-radio v-model="radio" label="1">備選項</el-radio>
上訴代碼相當于:
<el-radio :checked="radio" @change="val => { radio = val }" label="1">備選項</el-radio>
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" // 當前是否被禁用
-
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>
未選中
選中
- 外層,定位居中,判斷是否為禁用和選中
<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>
7. mixins
混入 (mixin) 提供了一種非常靈活的方式,來分發 Vue 組件中的可復用功能。一個混入對象可以包含任意組件選項。當組件使用混入對象時,所有混入對象的選項將被“混合”進入該組件本身的選項。