菜鳥讀element源碼六el-radio

開篇

花了快半個月時間來看radio組件,真的是發現本身基礎很薄弱,不少東西都不知道,仍是要多學習纔是。html

我發現直接把代碼放出來的效果並非很好,由於要結合着講解來才行。所以從本篇開始,我會放出我github的地址,我模仿的代碼都會放在該地址下,有興趣看的同窗能夠點連接vue

radio的主體結構

先來看一下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:hiddenElement是如何作的呢?github

設置 opacity:0radio的透明度設置爲 0,而且絕對定位 使其脫離文檔流,不會佔據空間。這既隱藏了 radio元素又不佔據 空間,而且可以獲取到焦點。是一個好方法,值得參考。

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-groupisGroup的值設置爲 truefalse。 再根據 isGroup來判斷 isDisabledide

isGrouptrue時, 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添加了如下修飾符

疑問一 passive究竟怎麼使用的誒?

vue還支持按鍵修飾符、鼠標鍵值修飾符、鍵值修飾符。詳細的解釋能夠看這篇文章,寫的很詳細。按鍵修飾符

介紹了按鍵修飾符,那麼這句代碼也就 不難理解了

@keydown.space.stop.prevent="model = isDisabled ? model : label"
複製代碼

tab選中當前 radio 在鍵盤上敲擊空格鍵的時候(space即空格鍵 ),阻止了原生事件發生(發生了什麼原生事件?這個不太知道),並執行代碼

model = isDisabled ? model : label   // 鍵盤選中radio
複製代碼

不知道大家看了 radio組件有沒有一個疑惑,就是沒有一個 click事件,卻在點擊的時候觸發了 model值的改變? 由於這塊有重寫v-model, 我打印了 modelsethandleChange

model: {
      get() {
      },
      set(val) {
        console.warn(val)
       }
    },
    
    handleChange() {
      console.log('change')
    }
複製代碼

在點擊事件觸發後執行順序 爲 set->handleChange

在這個地方咱們不得不說起一下 v-model的實現原理

v-model的本質是一個語法糖(幾乎說爛的詞),其本質是 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標籤上會監控不一樣屬性,拋出不一樣事件

  • text和textarea元素使用 value屬性和 input事件
  • checkboxradio元素使用 checked屬性和 change事件
  • selectvalue做爲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看了快半個月,大部分都花在源碼上了。望諸君一塊兒加油。有問題但願你們指出來!

相關文章
相關標籤/搜索