elementUI源碼分析-04-radio

一鲁纠、基礎(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;
    }
image.png

二之拨、el-radio用法與源碼

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-redio樣式

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;
}
空心 未選中轉(zhuǎn)臺(tái)的按鈕
// 選中狀態(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ū)別。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瓶籽,一起剝皮案震驚了整個(gè)濱河市匠童,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌塑顺,老刑警劉巖汤求,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異严拒,居然都是意外死亡扬绪,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門裤唠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)挤牛,“玉大人,你說(shuō)我怎么就攤上這事种蘸∧垢埃” “怎么了竞膳?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)诫硕。 經(jīng)常有香客問(wèn)我顶猜,道長(zhǎng),這世上最難降的妖魔是什么痘括? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任长窄,我火速辦了婚禮,結(jié)果婚禮上纲菌,老公的妹妹穿的比我還像新娘挠日。我一直安慰自己,他們只是感情好翰舌,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布嚣潜。 她就那樣靜靜地躺著,像睡著了一般椅贱。 火紅的嫁衣襯著肌膚如雪懂算。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天庇麦,我揣著相機(jī)與錄音计技,去河邊找鬼。 笑死山橄,一個(gè)胖子當(dāng)著我的面吹牛垮媒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播航棱,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼睡雇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了饮醇?” 一聲冷哼從身側(cè)響起它抱,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎朴艰,沒(méi)想到半個(gè)月后观蓄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呵晚,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年蜘腌,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饵隙。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撮珠,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情芯急,我是刑警寧澤勺届,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布,位于F島的核電站娶耍,受9級(jí)特大地震影響免姿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜榕酒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一胚膊、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧想鹰,春花似錦紊婉、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至何缓,卻和暖如春肢础,著一層夾襖步出監(jiān)牢的瞬間麻车,已是汗流浹背叹卷。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工颖低, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留棕硫,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓兔朦,卻偏偏與公主長(zhǎng)得像讳嘱,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子波材,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容