花了快半個月時間來看radio組件,真的是發現本身基礎很薄弱,不少東西都不知道,仍是要多學習纔是。html
我發現直接把代碼放出來的效果並非很好,由於要結合着講解來才行。所以從本篇開始,我會放出我github的地址,我模仿的代碼都會放在該地址下,有興趣看的同窗能夠點連接。vue
先來看一下radio組件的主體結構吧node
<template>
<label class="el-radio">
<span class="el-radio__input">
<span class="el-radio__inner"></span>
<input class="el-radio__original">
</span>
<!-- keydown.stop 阻止事件繼續冒泡 -->
<span class="el-radio__label" @keydown.stop>
<slot></slot>
<!-- 若是沒有設置radio顯示的值 則顯示label值 -->
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
複製代碼
一個 template
的代碼結構差很少就是這個樣子,使用 lable
標籤將這個文件包裹起來,擴大了點擊範圍,保證了點擊文字和圖標都可以起到點擊的效果。git
咱們能夠看到 radio
組件並無使用原生的radio
標籤,這是由於 原生標籤的 radio
在不一樣的瀏覽器下的樣式是並不相同,所以這裏是將 radio
隱藏起來,本身寫一個 radio
代替原生,統一各個瀏覽器樣式問題。 在這裏要說明的是,由於咱們須要用到 原生的radio
來獲取焦點並觸發 change
事件, 所以咱們不能將原生的 radio
設置爲 dispaly:none
或者 visibility:hidden
。Element
是如何作的呢?github
opacity:0
將
radio
的透明度設置爲
0
,而且絕對定位 使其脫離文檔流,不會佔據空間。這既隱藏了
radio
元素又不佔據 空間,而且可以獲取到焦點。是一個好方法,值得參考。
接下來我把 radio
的主體 template
放出來說解一下其中的屬性瀏覽器
<template>
<label
class="el-radio"
:class="[ // radio大小僅在border爲true時有效 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>
<!-- keydown.stop 阻止事件繼續冒泡 -->
<span class="el-radio__label" @keydown.stop>
<slot></slot>
<!-- 若是沒有設置radio顯示的值 則顯示label值 -->
<template v-if="!$slots.default">{{label}}</template>
</span>
</label>
</template>
複製代碼
role = 'radio'
:aria-checked="model===label"
:aria-disabled="isDisabled"
//這三行是爲了給不方便人士使用時提供功能的。好比當他們使用屏幕閱讀器的時候, role的做用是告訴閱讀這是一個 radio, aria-checked是描述這個 radio是否被選擇, aria-disabled是告訴閱讀器這個按鈕不可讀。
tabIndex="tabIndex" // 設置是否能夠經過鍵盤上的 tab鍵 進行選擇, -1 表明不可選, 0 表明可選
複製代碼
label
標籤上的 tabIndex
是經過計算獲得的,bash
isGroup() {
let parent = this.$parent
while (parent) {
if (parent.$options.componentName !== 'ElTestRadioGroup') {
parent = parent.$parent
} else {
// eslint-disable-next-line
this._radioGroup = parent
return true
}
}
return false
},
// 是否禁用
isDisabled() {
return this.isGroup
? this._radioGroup.disabled || this.disabled || (this.elTestForm || {}).disabled
: this.disabled || (this.elTestForm || {}).disabled
},
tabIndex() {
return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0
}
複製代碼
首先這裏經過遍歷 查詢當前radio
是否來被包裹於一個 radio-group
組件當中,根據是否包裹於 radio-group
將 isGroup
的值設置爲 true
、false
。 再根據 isGroup
來判斷 isDisabled
。ide
當 isGroup
爲 true
時, isDisabled
的值首先取決於 radioGroup
是否禁用,而後是radio
是否禁用, 最後有可能 radio
是位於一個 form
表單當中,取決於 form
的禁用狀態(看來form
會是一個 大難點哦)。 當爲 false
時,取決於 isGroup
函數
一樣的 tabIndex
值取決於 禁用狀態 , 而且當 radio
位於 radioGroup
時, 而且選中的是當前 radio
, 則保證使用 tab
鍵操做的時候,不會再次選擇,優化了體驗。學習
在這幾塊的計算判斷當中,值得注意的是它並無使用if else
判斷,而是使用了 與或的短路原則,值得學習一下。
&& 的判斷是同真爲真,一假爲假,則運算若是左邊的表達式值爲 false,那麼就不會再執行右邊的表達式了,若是左表達式爲 true,就會繼續執行右表達式 || 的判斷是一真爲真,同假爲假,則運算若是左表達式值爲 true,那麼就不用執行右邊的表達式了,若是左表達式爲 false,就會繼續執行右表達式;
template
標籤上還值得學習的一點是vue
的事件修飾符。事件修飾符。vue
爲事件v-on
添加了如下修飾符
vue還支持按鍵修飾符、鼠標鍵值修飾符、鍵值修飾符。詳細的解釋能夠看這篇文章,寫的很詳細。按鍵修飾符
介紹了按鍵修飾符,那麼這句代碼也就 不難理解了
@keydown.space.stop.prevent="model = isDisabled ? model : label"
複製代碼
當tab
選中當前 radio
在鍵盤上敲擊空格鍵的時候(space
即空格鍵 ),阻止了原生事件發生(發生了什麼原生事件?這個不太知道),並執行代碼
model = isDisabled ? model : label // 鍵盤選中radio
複製代碼
不知道大家看了 radio
組件有沒有一個疑惑,就是沒有一個 click事件,卻在點擊的時候觸發了 model
值的改變? 由於這塊有重寫v-model
, 我打印了 model
的 set
和 handleChange
model: {
get() {
},
set(val) {
console.warn(val)
}
},
handleChange() {
console.log('change')
}
複製代碼
在點擊事件觸發後執行順序 爲 set->handleChange
。
在這個地方咱們不得不說起一下 v-model
的實現原理
v-mode
l的本質是一個語法糖(幾乎說爛的詞),其本質是 v-bind
v-on
的組合,如下兩種狀況是相等的
<input v-model="test"></input>
<input v-bind:value="test" v-on:input = "test = $event.target.value"
咱們來看一下爲何這兩種狀況是相等的。
從源碼的角度,咱們使用v-model
的時候實際上是觸發了這個函數
function genRadioModel (
el: ASTElement,
value: string,
modifiers: ?ASTModifiers
) {
const number = modifiers && modifiers.number
let valueBinding = getBindingAttr(el, 'value') || 'null'
valueBinding = number ? `_n(${valueBinding})` : valueBinding
addProp(el, 'checked', `_q(${value},${valueBinding})`)
addHandler(el, 'change', genAssignmentCode(value, valueBinding), null, true)
}
複製代碼
你可能會發現 爲何添加的是 checked
屬性 和change
事件
其實v-model
在不一樣的 HTML
標籤上會監控不一樣屬性,拋出不一樣事件
tex
t和textarea
元素使用 value
屬性和 input
事件checkbox
和radio
元素使用 checked
屬性和 change
事件select
將 value
做爲prop
並將change
做爲事件v-model
使用 value
屬性 和 input
事件 咱們能夠看到源碼上函數genRadioModel
確實是給 input
組件添加了 checked
屬性 和change
事件。 源碼上對於input
標籤的不一樣類型也是作不一樣的處理(我這裏放出部分代碼),詳細的過程有興趣的同窗能夠去看VUE
的源碼 或者 去看 vue.js技術揭祕 好書值得一看!!!強推if (el.component) {
genComponentModel(el, value, modifiers)
// component v-model doesn't need extra runtime return false } else if (tag === 'select') { genSelect(el, value, modifiers) } else if (tag === 'input' && type === 'checkbox') { genCheckboxModel(el, value, modifiers) } else if (tag === 'input' && type === 'radio') { genRadioModel(el, value, modifiers) } else if (tag === 'input' || tag === 'textarea') { genDefaultModel(el, value, modifiers) } else if (!config.isReservedTag(tag)) { genComponentModel(el, value, modifiers) // component v-model doesn't need extra runtime
return false
} else if (process.env.NODE_ENV !== 'production') {
warn(
`<${el.tag} v-model="${value}">: ` +
`v-model is not supported on this element type. ` +
'If you are working with contenteditable, it\'s recommended to ' + 'wrap a library dedicated for that purpose inside a custom component.', el.rawAttrsMap['v-model'] ) } 複製代碼
因此原生代碼時 <input type="radio" v-model="value">
通過處理後 按照 源碼的流程,將radio
其實處理爲 <input v-bind:checked="value" v-on:change="value = $event.target.checked">
所以當咱們引入 radio
組件並使用的時候,咱們的代碼是這樣
<el-radio v-model="radio"></el-radio>
等價於
<el-radio v-bind:value="radio" v-on:input="radio = #event.tagret.value"></el-radio>
複製代碼
在代碼中的原生 input
標籤
<input
...
v-model="model"
type="radio"
....
>
複製代碼
則轉化爲
<input v-bind: checked="model" v-on: change="model=$event.target.checked">
複製代碼
當 原生 radio
被點擊時, model
的值發生改變,觸發 set
model: {
get() {
// 若是是以el-radio-group包裹 則取group的value值
return this.isGroup ? this._radioGroup.value : this.value
},
set(val) {
if (this.isGroup) {
this.dispatch('ElTestRadioGroup', 'input', [val])
} else {
this.$emit('input', val)
}
this.$refs.radio && (this.$refs.radio.checked = this.model === this.label)
}
},
複製代碼
而後調用 this.$emit('input')
將值傳遞到 咱們自定義的radio
組件上,進而改變咱們綁定的 radio
值。 利用了 on/emit
監聽傳遞事件。
此時若是是radio-group
時,會觸發 dispatch
事件,在非group
時,觸發 input事件。
<input
...
@focus="focus = true"
@blur="focus=false"
@change="handleChange"
...
>
複製代碼
最後就是在鼠標焦點聚焦 radio
以及移開時 觸發 focus
以及 blur
事件, 當model
的值發生改變時會觸發 handleChange
事件
handleChange() {
this.$nextTick(() => {
this.$emit('change', this.model)
// 根據是不是group調用 分發dispatch事件
this.isGroup && this.dispatch('ElTestRadioGroup', 'handleChange', this.model)
})
}
複製代碼
this.$emit('change', this.model)
則是若是組件有自定義的 change
事件時,將會觸發自定義的change
事件。
如何對vue源碼中打斷點呢? 在運行時,vue執行的是node_modules/vue/dist/vue.runtime.ems.js, 可是dis`是打包後的文件,我如何對 src/...裏面 分開的單個文件打斷點並查看?
好比說這樣一個文件,model.js,用來處理 v-model指令的,我怎麼打斷點而後在運行時查看呢?求教
其實 radio
其實還引用了 mixins
屬性(混入), 由於在單個的 radio
中並未觸發到 混入文件 emitter.js
中的函數,所以我會放在下一篇 radio-group
時來根據事件觸發的順序講一下 混入函數的觸發過程。
感受能夠開始閱讀vue
源碼了,不少東西仍是要和源碼結合才能看懂誒,一個radio
看了快半個月,大部分都花在源碼上了。望諸君一塊兒加油。有問題但願你們指出來!