一鲁纠、基礎(chǔ)回顧
el-radio是單選組件,是對(duì)原生<input type="radio">
的封裝。
先來(lái)回顧原生的radio單選悯周,比如做一個(gè)單選題的單選按鈕
<input type="radio" name ="fruit" value="apple" checked>大蘋(píng)果
<input type="radio" name ="fruit" value="banana">大香蕉
checked
:表示被選中的
value
: 表示單選選項(xiàng)的值
name
: 定義 input 元素的名稱,具有相同name可以達(dá)到互斥的效果,表示同一時(shí)刻只能有一個(gè)按鈕被選中
獲取value的值陪竿,可以通過(guò)click禽翼、onchange等事件遍歷元素屠橄,checked=true的那一項(xiàng)的value值,就是在最后選中的value值闰挡。
有時(shí)候還會(huì)input和label配合使用锐墙,label 元素不會(huì)向用戶呈現(xiàn)任何特殊效果,label的for屬性應(yīng)當(dāng)與相關(guān)元素的 id 屬性相同,input配合label使用可以點(diǎn)擊label的內(nèi)容长酗,聚焦到input
<input type="radio" name ="fruit" id="apple" checked value="apple">
<label for="apple">大蘋(píng)果</label>
<input type="radio" name ="fruit" id="banana" value="banana">
<label for="banana">大香蕉</label>
因?yàn)樵鷨芜x在不同瀏覽器下的默認(rèn)顯示效果不一樣溪北,所以通常情況下,我們都會(huì)采用障眼法覆蓋其原生的樣式夺脾。
label{
line-height: 24px;
height: 24px;
display: inline-block;
margin-left: 5px;
margin-right:15px;
color: #777;
}
.radio_type{
width: 20px;
height:20px;
appearance: none;
-moz-appearance:none; /* Firefox */
-webkit-appearance:none; /* Safari 和 Chrome */
position: relative;
}
.radio_type:before{
content: '';
width: 20px;
height: 20px;
border: 2px solid #EDD19D;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
}
.radio_type:checked:before{
content: '';
width: 20px;
height: 20px;
border: 2px solid #EDD19D;
display: inline-block;
border-radius: 50%;
vertical-align: middle;
}
.radio_type:checked:after{
content: '';
width: 12px;
height: 12px;
text-align: center;
background:#EDD19D;
border-radius: 50%;
display: block;
position: absolute;
top: 6px;
left: 6px;
}
.radio_type:checked+label{
color: #EDD19D;
}
二之拨、el-radio用法與源碼
基礎(chǔ)的引用方式如下:
<template>
<el-radio v-model="radio" label="1">備選項(xiàng)</el-radio>
<el-radio v-model="radio" label="2">備選項(xiàng)</el-radio>
</template>
<script>
export default {
data () {
return {
radio: '1'
};
}
}
</script>
接受的參數(shù)如下
參數(shù) | 說(shuō)明 | 類型 | 可選值 | 默認(rèn)值 |
---|---|---|---|---|
value / v-model | 綁定值 | string / number / boolean | — | — |
label | Radio 的 value | string / number / boolean | — | — |
disabled | 是否禁用 | boolean | — | false |
border | 是否顯示邊框 | boolean | — | false |
size | Radio 的尺寸,僅在 border 為真時(shí)有效 | string | medium / small / mini | — |
name | 原生 name 屬性 | string | — | — |
源碼如下
<template>
<label
class="el-radio"
:class="[
border && radioSize ? 'el-radio--' + radioSize : '',
{ 'is-disabled': isDisabled },
{ 'is-focus': focus },
{ 'is-bordered': border },
{ 'is-checked': model === label }
]"
role="radio"
:aria-checked="model === label"
:aria-disabled="isDisabled"
:tabindex="tabIndex"
@keydown.space.stop.prevent="model = isDisabled ? model : label"
>
<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"
@focus="focus = true"
@blur="focus = false"
@change="handleChange"
:name="name"
:disabled="isDisabled"
tabindex="-1"
>
</span>
<span class="el-radio__label" @keydown.stop>
<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: {
// 判斷當(dāng)前組件的父組件是否是ElRadioGroup(單選框組)
isGroup() {
let parent = this.$parent;
while (parent) {
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent;
} else {
this._radioGroup = parent;
return true;
}
}
return false;
},
model: {
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() {
return (this.elFormItem || {}).elFormItemSize;
},
radioSize() {
const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
return this.isGroup
? this._radioGroup.radioGroupSize || temRadioSize
: temRadioSize;
},
isDisabled() {
return this.isGroup
? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
: this.disabled || (this.elForm || {}).disabled;
},
tabIndex() {
return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
}
},
methods: {
handleChange() {
this.$nextTick(() => {
this.$emit('change', this.model);
this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
});
}
}
};
</script>
name
:用于給原生input元素的設(shè)置name屬性,用于達(dá)到相同name互斥的效果
border
: 是否為選項(xiàng)添加邊框咧叭,如果為true蚀乔,則設(shè)置is-bordered類名為元素添加邊框
.el-radio.is-bordered {
padding: 12px 20px 0 10px;
border-radius: 4px;
border: 1px solid #dcdfe6;
box-sizing: border-box;
height: 40px;
}
size
:與border配合使用時(shí)才生效,用來(lái)設(shè)置選項(xiàng)按鈕大小
disabled
:給原生input元素設(shè)置disable屬性菲茬,使其禁用吉挣。
label
:Radio 的 value值
value / v-model
:用來(lái)實(shí)現(xiàn)雙向數(shù)據(jù)綁定的。
三婉弹、功能點(diǎn)解密
樣式設(shè)置
el-radio也是覆蓋了radio原有的樣式听想。label下面嵌套了2個(gè)span,第一個(gè)span是圖標(biāo)部分马胧,第二個(gè)span是文字部分汉买,
第一個(gè)span中又嵌套了一個(gè)span和input,里面的span是模擬的圓形按鈕佩脊,input是真正的radio標(biāo)簽蛙粘,el-input隱藏了原有的input樣式,然后用span標(biāo)簽去模擬input標(biāo)簽威彰。
隱藏原生input的樣式,將其設(shè)置opacity為0出牧,使其在頁(yè)面中不可見(jiàn),但是可以點(diǎn)擊
.el-radio__original {
opacity: 0;
outline: none;
position: absolute;
z-index: -1;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: 0;
設(shè)置span的樣式歇盼,模擬input
// 空心,未選中轉(zhuǎn)臺(tái)的按鈕
.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;
}
// 選中狀態(tài)下的按鈕
el-radio__input.is-checked .el-radio__inner {
border-color: #409eff;
background: #409eff;
}
.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;
}
// 用偽類舔痕,模擬中間小圓點(diǎn)
.el-radio__input.is-checked .el-radio__inner:after {
transform: translate(-50%,-50%) scale(1);
}
.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;
}
v-model/value
單獨(dú)引用el-radio組件的時(shí)候,都會(huì)使用v-model去綁定data下面的值來(lái)實(shí)現(xiàn)雙向數(shù)據(jù)綁定豹缀,也就是說(shuō)有時(shí)候即使我們不設(shè)置name屬性伯复,也可以達(dá)到互斥的效果,所以我們?nèi)タ聪?code>v-model/value是如何實(shí)現(xiàn)的呢邢笙?
其實(shí)v-model/value只是v-bind和v-on的語(yǔ)法糖啸如,在使用v-model綁定數(shù)據(jù)以后,既綁定了數(shù)據(jù)氮惯,又添加了事件監(jiān)聽(tīng)叮雳,這個(gè)事件就是input事件想暗。
官方文檔給出:
<input v-model="something">
這不過(guò)是以下示例的語(yǔ)法糖:
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
這就相當(dāng)于對(duì)input元素的input事件進(jìn)行監(jiān)聽(tīng),來(lái)實(shí)現(xiàn)value值的綁定帘不,至于如何實(shí)現(xiàn)互斥说莫,其實(shí)就是如果單選框的value值和v-model值相同,那么就給當(dāng)前input元素添加一個(gè)checked屬性寞焙,表示被選中储狭,其他不相等的,就不添加checked屬性棺弊,就實(shí)現(xiàn)了互斥的效果晶密。
在el-radio的源碼中擒悬,對(duì)input設(shè)置的是v-model="model"模她,并且對(duì)model設(shè)置了getter和setter,然后emit一個(gè)input事件懂牧,在官網(wǎng)中可以看到解釋.
允許一個(gè)自定義組件在使用 v-model 時(shí)定制 prop 和 event侈净。默認(rèn)情況下,一個(gè)組件上的 v-model 會(huì)把 value 用作 prop 且把 input 用作 event僧凤。
<my-checkbox v-model="foo" value="some value"></my-checkbox>
上述代碼相當(dāng)于
<my-checkbox
:checked="foo"
@change="val => { foo = val }"
value="some value">
</my-checkbox>
tabindex和:aria-&
tabIndex和aria-* 都是屬于無(wú)障礙學(xué)習(xí)的一些設(shè)置畜侦。
html中的tabIndex屬性可以設(shè)置鍵盤中的TAB鍵在控件中的移動(dòng)順序,即焦點(diǎn)的順序。幾乎所有瀏覽器均 tabindex 屬性躯保,除了 Safari旋膳。
tabindex有三個(gè)值:0 ,-1途事, 以及X(X里32767是界點(diǎn))验懊。
當(dāng)tabindex>=1時(shí),該元素可以用tab鍵獲取焦點(diǎn),數(shù)字越小尸变,越先定位到义图。
tabIndex=0 ,將排列在所有tabIndex>=1的控件之后召烂。默認(rèn)情況下tabIndex=0
當(dāng)tabindex=-1時(shí)碱工,該元素用tab鍵獲取不到焦點(diǎn),但是可以通過(guò)js獲取.
支持 tabindex 屬性的元素:<a>, <area>, <button>, <input>, <object>, <select> 以及 <textarea>奏夫。
可以用以下代碼怕篷,使用tab鍵感受以下。
<a href="0.com" tabindex="0">0</a>
<a href="-1.com" tabindex="-1">-1</a>
<a href="1.com" tabindex="1">1</a>
<a href="2.com" tabindex="2">2</a>
<a href="3.com" tabindex="3">3</a>
role酗昼、aria-checked匙头、aria-disabled,這些是為屏幕閱讀器準(zhǔn)備的仔雷,aria由一套屬性組成蹂析,屬性分為role以及對(duì)應(yīng)的states和properties舔示,aria將html元素分為六種role,每種有對(duì)應(yīng)的states和properties电抚,用以模擬一些tag惕稻,更詳細(xì)的可點(diǎn)次查看《aria初探(一)》
@keydown.space.stop.prevent="model = isDisabled ? model : label"
這句也很巧妙,查了才知道蝙叛,原來(lái)是為了tab切換不同選項(xiàng)時(shí)俺祠,按空格可以快速選擇目標(biāo)項(xiàng)。
mixin
mixin用來(lái)封裝vue組件的可復(fù)用功能借帘,一個(gè)混入對(duì)象可以包含任意組件選項(xiàng)蜘渣。當(dāng)組件使用混入對(duì)象時(shí),所有混入對(duì)象的選項(xiàng)將被“混合”進(jìn)入該組件本身的選項(xiàng)肺然。
當(dāng)組件和混入對(duì)象含有同名選項(xiàng)時(shí)蔫缸,這些選項(xiàng)將以恰當(dāng)?shù)姆绞竭M(jìn)行“合并”。
- 數(shù)據(jù)對(duì)象在內(nèi)部會(huì)進(jìn)行遞歸合并际起,并在發(fā)生沖突時(shí)以組件數(shù)據(jù)優(yōu)先拾碌。
- 值為對(duì)象的選項(xiàng),例如 methods街望、components 和 directives校翔,將被合并為同一個(gè)對(duì)象。兩個(gè)對(duì)象鍵名沖突時(shí)灾前,取組件對(duì)象的鍵值對(duì)防症。
這里是混入了一個(gè)Emitter,也就是說(shuō)該組件擁有Emitter中的方法哎甲。
function broadcast(componentName, eventName, params) {
this.$children.forEach(child => {
var name = child.$options.componentName;
if (name === componentName) {
child.$emit.apply(child, [eventName].concat(params));
} else {
broadcast.apply(child, [componentName, eventName].concat([params]));
}
});
}
export default {
methods: {
dispatch(componentName, eventName, params) {
var parent = this.$parent || this.$root;
var name = parent.$options.componentName;
while (parent && (!name || name !== componentName)) {
parent = parent.$parent;
if (parent) {
name = parent.$options.componentName;
}
}
if (parent) {
parent.$emit.apply(parent, [eventName].concat(params));
}
},
broadcast(componentName, eventName, params) {
broadcast.call(this, componentName, eventName, params);
}
}
};
Emitter的源碼中蔫敲,是在methods混入了dispatch、broadcast兩個(gè)方法烧给,說(shuō)明在其他的組件中也會(huì)多次使用這個(gè)兩個(gè)方法燕偶。
dispatch
dispatch接受三個(gè)參數(shù)、componentName組件名础嫡,eventName事件名指么,params事件參數(shù)、
dispatch主要作用就是找到距離自己最近的目標(biāo)父組件榴鼎,然后調(diào)用目標(biāo)組件的 目標(biāo)事件伯诬,并傳遞參數(shù)。
這里調(diào)用目標(biāo)事件使用的是parent.$emit.apply(parent, [eventName].concat(params));
, 你可能會(huì)有疑問(wèn)巫财,為什么這么調(diào)用盗似,不直接parent.$emit(eventName,...params)
?
首先平项,vm.$emit( event, arg )的作用是觸發(fā)當(dāng)前實(shí)例上的事件赫舒,apply主要作用是改變this指向悍及,那么那個(gè)調(diào)用方式就是用parent對(duì)象去調(diào)用parent對(duì)象的eventName。
即parent拿到parent的$emit
方法接癌,再傳遞對(duì)應(yīng)的事件參數(shù)心赶。
broadcast
broadcast也接受三個(gè)參數(shù)、componentName組件名缺猛,eventName事件名缨叫,params事件參數(shù)、
broadcast主要作用是像后代組件傳值荔燎,會(huì)遍歷所有后代組件耻姥,如果是目標(biāo)組件,就調(diào)用目標(biāo)子組件的目標(biāo)方法有咨,并傳遞參數(shù)琐簇。
與dispatch類似。
四摔吏、button-group鸽嫂、radio-button
el-radio中很多地方都計(jì)算了isGroup纵装,這是因?yàn)閑le還提供了一個(gè)el-radio-group組件征讲,適用于在多個(gè)互斥的選項(xiàng)中選擇的場(chǎng)景,所以在設(shè)置class或者橡娄,觸發(fā)input事件時(shí)都先判斷是否是isGroup诗箍,如果isGroup,那么就采用isGroup的值或者事件挽唉。
radio-button和el-radio功能一樣滤祖,只是樣式的區(qū)別。