button.vuejavascript
<template> <button class="el-button" @click="handleClick" :disabled="buttonDisabled || loading" :autofocus="autofocus" :type="nativeType" :class="[ type ? 'el-button--' + type : '', buttonSize ? 'el-button--' + buttonSize : '', { 'is-disabled': buttonDisabled, 'is-loading': loading, 'is-plain': plain, 'is-round': round, 'is-circle': circle } ]" > <i class="el-icon-loading" v-if="loading"></i> <i :class="icon" v-if="icon && !loading"></i> <span v-if="$slots.default"><slot></slot></span> </button> </template> <script> export default { name: 'ElButton', //provider/inject:簡單的來講就是在父組件中經過provider來提供變量,而後在子組件中經過inject來注入變量 inject: { elForm: { default: '' }, elFormItem: { default: '' } }, props: { type: { //按鈕類型 type: String, default: 'default' }, size: String, //按鈕尺寸 icon: { //圖標類名 type: String, default: '' }, nativeType: { //原生type類型 type: String, default: 'button' }, loading: Boolean,//是否加載中狀態 disabled: Boolean,//是否禁用狀態 plain: Boolean, //是否樸素按鈕 autofocus: Boolean, //是否默認聚焦 round: Boolean,//是否圓角按鈕 circle: Boolean //是否圓形按鈕 }, computed: { //父組件中的變量 _elFormItemSize() { return (this.elFormItem || {}).elFormItemSize; }, //按鈕尺寸計算 buttonSize() { return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size; }, //按鈕是否禁用 buttonDisabled() { return this.disabled || (this.elForm || {}).disabled; } }, methods: { handleClick(evt) { this.$emit('click', evt); } } }; </script>
radio.vuehtml
<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 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: { 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); } } }, _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>
解析:vue
(1)組件的html結構java
<label ...> <span class='el-radio__input'> <span class='el-radio__inner'></span> <input type='radio' .../> </span> <span class='el-radio__label'> <slot></slot> <template v-if="!$slots.default">{{label}}</template> </span> </label>
整個組件是一個外層label嵌套兩個span。label放在最外面的做用是擴大鼠標點擊範圍,點擊文字或者input都能觸發響應。
第一個 表示圓形按鈕,裏面的span就是模擬的圓圈,input纔是真正的radio按鈕,經過樣式隱藏了。
第二個表示文字部分。
這篇博文中有詳細的說明:http://www.javashuo.com/article/p-ewxwlfye-ev.html 強烈推薦!數組
(2)label部分app
<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" >
一、 border && radioSize ? 'el-radio--' + radioSize : '',
radioSize 是Radio 的尺寸,僅在 border 爲真時有效。radioSize是計算屬性ide
radioSize() { // props 的 size 及 form 注入的 size 及全局配置對象 ($ELEMENT, 此對象由引入時 Vue.use() 傳入的默認空對象)的 size const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size; // 經過isGroup判斷是否被 radio-group 組件包裹,若是包裹優先 radioGroupSize return this.isGroup ? this._radioGroup.radioGroupSize || temRadioSize : temRadioSize; }
isGroup:獲取當前組件的父級組件,而後檢查其組件名是不是ElRadioGroup即單選框組,若是不是就繼續檢查父級的父級。這個方法會找到距離本身最近的父級ElRadioGroup組件,有則true,無則falsewordpress
isGroup() { let parent = this.$parent; while (parent) { if (parent.$options.componentName !== 'ElRadioGroup') { parent = parent.$parent; } else { this._radioGroup = parent; return true; } } return false; }
二、{ 'is-disabled': isDisabled } 表示是否禁用radio組件,與尺寸相似,就不說了。
三、 { 'is-checked': model === label } 表示當前按鈕是否被選中,經過 model === label 控制。這裏看下model:源碼分析
model: { get() { //取值 //經過isGroup判斷是否被 radio-group 組件包裹,若是是就返回 radio-group的value:若是不是就返回本身的value return this.isGroup ? this._radioGroup.value : this.value; }, set(val) { //賦值 if (this.isGroup) { // 被 radio-group 組件包裹 ,radio-group 組件發佈 input 事件,傳遞值 this.dispatch('ElRadioGroup', 'input', [val]); } else { // 沒有被 radio-group 組件包裹, 觸發父組件上的input事件傳遞出val this.$emit('input', val); } } }
四、WAI-ARIA無障礙網頁應用屬性
role="radio" :aria-checked="model === label" :aria-disabled="isDisabled"
WAI-ARIA指無障礙網頁應用。主要針對的是視覺缺陷,失聰,行動不便的殘疾人以及僞裝殘疾的測試人員。尤爲像盲人,眼睛看不到,其瀏覽網頁則須要藉助輔助設備,如屏幕閱讀器,屏幕閱讀機能夠大聲朗讀或者輸出盲文。而ARIA就是可讓屏幕閱讀器準確識別網頁中的內容,變化,狀態的技術規範,可讓盲人這類用戶也能無障礙閱讀!
五、:tabindex="tabIndex" :控制tab是否能夠選中
tabIndex() { //禁用狀態或者單選按鈕是在單選框組組件內且不是選中狀態時,返回 -1; return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0; }
當tabIndex=0時,該元素能夠用tab鍵獲取焦點,且訪問順序是按照元素在文檔中的順序來獲取焦點;當tabIndex=1時,該元素用tab鍵獲取不到焦點;當 tabindex > 0 的元素都切換以後,纔會切換到 tabindex = 0 的元素,而且按出現的前後次序進行切換,這裏的邏輯就是tab只能訪問到選中狀態下的單選按鈕
六、@keydown.space.stop.prevent="model = isDisabled ? model : label"
空格鍵按下的時候給model賦值,可是不知道有什麼用,不知道在什麼場景下用獲得。
(3)混入選項
import Emitter from 'element-ui/src/mixins/emitter'; mixins: [Emitter],
這裏主要是用到了dispatch方法
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) { //必須用apply定$emit的調用目標對象,由於是在父組件上觸發該事件而不是在dispatch裏 parent.$emit.apply(parent, [eventName].concat(params)); } },
第一個參數是父級組件的名稱,第二個是事件名稱,第三個參數是一個數組或者單獨的值,邏輯也很簡單:不斷地取到本身的父組件,判斷是不是目標組件,若是不是繼續去其父組件判斷,若是是則在父組件上調用$emit觸發事件。
handleChange() { this.$nextTick(() => { this.$emit('change', this.model); //找到父組件去觸發父組件的handleChange事件 this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model); }); }
this.$on('handleChange', value => { //在radio-group去觸發父組件的change事件 this.$emit('change', value); });
參考博文:
一、無障礙閱讀:https://www.zhangxinxu.com/wordpress/2012/03/wai-aria-無障礙閱讀/
二、Element源碼分析系列4-Radio(單選框):http://www.javashuo.com/article/p-ewxwlfye-ev.html
三、拜讀及分析 Element 源碼 - radio 組件篇:http://www.h3399.cn/201809/614690.html