Button組件 button.vue Radio組件 radio.vue 解析: (1)組件的html結構 整個組件是一個外層label嵌套兩個span。label放在最外面的作用是擴大滑鼠點擊範圍,點擊文字或者input都能觸發響應。 第一個 表示圓形按鈕,裡面的span就是模擬的圓圈,inpu ...
Button組件
button.vue
<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組件
radio.vue
<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>
解析:
(1)組件的html結構
<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按鈕,通過樣式隱藏了。
第二個表示文字部分。 預設渲染間的文本; 表示如果沒有文本,但是
這篇博文中有詳細的說明:https://juejin.im/post/5b7a9d61e51d4538e5679462 強烈推薦!
(2)label部分
<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"
>
1、 border && radioSize ? 'el-radio--' + radioSize : '',
radioSize 是Radio 的尺寸,僅在 border 為真時有效。radioSize是計算屬性
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,無則false
isGroup() {
let parent = this.$parent;
while (parent) {
if (parent.$options.componentName !== 'ElRadioGroup') {
parent = parent.$parent;
} else {
this._radioGroup = parent;
return true;
}
}
return false;
}
2、{ 'is-disabled': isDisabled } 表示是否禁用radio組件,與尺寸類似,就不說了。
3、 { '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);
}
}
}
4、WAI-ARIA無障礙網頁應用屬性
role="radio"
:aria-checked="model === label"
:aria-disabled="isDisabled"
WAI-ARIA指無障礙網頁應用。主要針對的是視覺缺陷,失聰,行動不便的殘疾人以及假裝殘疾的測試人員。尤其像盲人,眼睛看不到,其瀏覽網頁則需要藉助輔助設備,如屏幕閱讀器,屏幕閱讀機可以大聲朗讀或者輸出盲文。而ARIA就是可以讓屏幕閱讀器準確識別網頁中的內容,變化,狀態的技術規範,可以讓盲人這類用戶也能無障礙閱讀!
5、: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只能訪問到選中狀態下的單選按鈕
6、@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);
});
參考博文:
1、無障礙閱讀:https://www.zhangxinxu.com/wordpress/2012/03/wai-aria-無障礙閱讀/
2、Element源碼分析系列4-Radio(單選框):https://juejin.im/post/5b7a9d61e51d4538e5679462
3、拜讀及分析 Element 源碼 - radio 組件篇:http://www.h3399.cn/201809/614690.html