天天看點

element-plus 源碼學習——radioradioradio-groupradio-button

radio

  1. label

    放在最外層的作用是

    擴大滑鼠點選範圍

    ,無論是點選在文字還是input上都能夠觸發響
    <label class="el-radio">
      <!-- 單選框 -->
      <span class="el-radio__input"></span>
      <!-- 文字部分 -->
      <span class="el-radio__label"></span>
    </label>
               
    <template>
     <label
       class="el-radio"
       :class="{
         [`el-radio--${radioSize || ''}`]: border && 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"
     >
       <!-- :tabindex="tabIndex"
       @keydown.space.stop.prevent="model = isDisabled ? model : label"
       這個功能是為了用tab切換不同選項時,按空格可以快速選擇目标項 -->
    
       <!-- 模拟圓形按鈕 -->
       <span
         class="el-radio__input"
         :class="{
           'is-disabled': isDisabled,
           'is-checked': model === label
         }"
       >
         <!-- 圓形樣式 -->
         <span class="el-radio__inner"></span>
         <!-- 真正的按鈕 -->
         <!-- el-radio__original ⚠️注意這個樣式 -->
         <input
           ref="radioRef"
           v-model="model"
           class="el-radio__original"
           :value="label"
           type="radio"
           aria-hidden="true"
           :name="name"
           :disabled="isDisabled"
           tabindex="-1"
           @focus="focus = true"
           @blur="focus = false"
           @change="handleChange"
         >
       </span>
       <!-- 文字部分 -->
       <!-- keydown.stop 阻止事件繼續冒泡 -->
       <span class="el-radio__label" @keydown.stop>
         <slot>
           {{ label }}
         </slot>
       </span>
     </label>
    </template>
               
  2. el-radio__original
    .el-radio__original {
        opacity: 0;
        outline: none;
        position: absolute;
        z-index: -1;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: 0;
    }
               
    真正的input透明度為0,且是絕對定位脫離文檔流,是以我們看不到,但是是有大小的,注意不是

    display:none

    或者

    visibility:hidden

    ,如果是none或者hidden的話則無法觸發滑鼠點選了,隻有

    opacity:0

    才能達到目的,這是個需要注意的地方
    element-plus 源碼學習——radioradioradio-groupradio-button
  3. disabled
    樣式上的

    disabled

    綁定在

    label

    上,功能上的

    disabled

    綁定在

    input

    const isDisabled = computed(() => {
       return isGroup.value
         ? radioGroup.disabled || props.disabled || elForm.disabled
         : props.disabled || elForm.disabled
     })
               

    isGroup

    elForm

    的取值都是通過

    inject

    注入
    const radioGroup = inject(radioGroupKey, {} as RadioGroupContext)
    const isGroup = computed(() => radioGroup?.name === 'ElRadioGroup')
               
  4. role="radio"
    :aria-checked="model === label"
    :aria-disabled="isDisabled"
               

    role

    role的作用是描述一個非标準的tag的實際作用。比如用div做button,那麼設定div 的 role=“button”,輔助工具就可以認出這實際上是個button。

    aria-*

    的作用就是描述這個tag在可視化的情境中的具體資訊
    此tag 是一個

    radio

    checked

    disabled

    的屬性分别是什麼
  5. :tabindex="tabIndex"
    @keydown.space.stop.prevent="model = isDisabled ? model : label"
               
    tabindex規定了按下tab鍵該元素擷取焦點的順序,同樣是個計算屬性
    const tabIndex = computed(() => {
        return (isDisabled.value || (isGroup.value && model.value !== props.label)) ? -1 : 0
      })
               
    • tabindex=負值 (通常是tabindex=“-1”),表示元素是可聚焦的,但是不能通過鍵盤導航來通路到該元素,用JS做頁面小元件内部鍵盤導航的時候非常有用。
    • tabindex="0"

      ,表示元素是可聚焦的,并且可以通過鍵盤導航來聚焦到該元素,它的相對順序是目前處于的DOM結構來決定的。
    • tabindex=正值,表示元素是可聚焦的,并且可以通過鍵盤導航來通路到該元素;它的相對順序按照tabindex 的數值

      遞增而滞後獲焦

      。如果多個元素擁有相同的 tabindex,它們的相對順序按照他們在目前DOM中的先後順序決定。

    @keydown.space.stop.prevent="model = isDisabled ? model : label"

    ​ 這個功能是為了用tab切換不同選項時,按空格可以快速選擇目标項 (先通過tab 切換聚焦,在通過space選擇目前聚焦的項目)
  6. focus
    //radio.vue
    const focus = ref(false)//控制focus樣式 click時,不顯示focus樣式
    
    <label  :class="{
          'is-focus': focus,
          ...
        }">
    
    <input @focus="focus = true"
           @blur="focus = false">
               
    .el-radio:focus:not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner {
        box-shadow: 0 0 2px 2px #409eff;
    }
               

    tab切換

    ,會出現

    foucs

    樣式,但不會觸發

    @focus事件

    因為label上添加的

    :tabindex="tabIndex"

    ,而input的

    :tabindex="-1"

    ,是以雖然有樣式,但不會觸發input的

    @focus事件

    click

    時,同時也會出現

    focus

    樣式,觸發

    @focus事件

    ,此時不想有

    focus

    的樣式,是以添加一個

    is-focus

  7. model
    const model = computed<string | number | boolean>({
          get() {
            return isGroup.value ? radioGroup.modelValue : props.modelValue
          },
          set(val) {
            if (isGroup.value) {
              radioGroup.changeEvent(val)//injdect radioGroup 修改radioGroup的v-model值
            } else {
              ctx.emit(UPDATE_MODEL_EVENT, val)//update:modelValue 修改radio的v-model值
            }
            radioRef.value.checked = props.modelValue === props.label//dom 對象上的 checked 屬性
          },
        })
               

    Model

    通過

    computed

    計算而來,

    get : 擷取值

    Set: 修改值

  8. nextTick
    function handleChange() {
      nextTick(() => {
        ctx.emit('change', model.value)
      })
    }
               
    此處為什麼使用nextTick?感覺不使用也沒問題❓
    我認為 radio-group 中已確定了執行循序,是以radio的change 無論是單獨使用還是在group内總是最後執行的,是以我認為使不使用

    nextTick

    都可以。希望有人幫忙解惑,謝謝

radio-group

  1. nextTick
    const changeEvent = value => {
          ctx.emit(UPDATE_MODEL_EVENT, value)//update:modelValue
          nextTick(() => {
            ctx.emit('change', value)
          })
        }
               
    此處nextTick作用,保證執行順序。 先 ctx.emit(UPDATE_MODEL_EVENT, value),後 ctx.emit(‘change’, value),保證

    v-model

    變化會優先于

    change

    執行。

    如果沒有 nextTick,執行順序相反,

    因為ctx.emit(UPDATE_MODEL_EVENT, value),更新值的變化,會導緻頁面更新,是以會在

    下次頁面更新時執行

    。而chang事件會在

    目前執行

  2. 選中切換
    // '@element-plus/utils/aria'
    export const EVENT_CODE = {
      tab: 'Tab',
      enter: 'Enter',
      space: 'Space',
      left: 'ArrowLeft', // 37
      up: 'ArrowUp', // 38
      right: 'ArrowRight', // 39
      down: 'ArrowDown', // 40
      esc: 'Escape',
      delete: 'Delete',
      backspace: 'Backspace',
    }
    //radio-group.vue
      const handleKeydown = e => { // 左右上下按鍵 可以在radio組内切換不同選項
          const target = e.target
          const className = target.nodeName === 'INPUT' ? '[type=radio]' : '[role=radio]'//radio||label
          const radios = radioGroup.value.querySelectorAll(className)
          const length = radios.length
          const index = Array.from(radios).indexOf(target)
          const roleRadios = radioGroup.value.querySelectorAll('[role=radio]')
          let nextIndex = null
          switch (e.code) {
            case EVENT_CODE.left:
            case EVENT_CODE.up:
              //⬅️⬆️ 選中前一個
              e.stopPropagation()//阻止冒泡
              e.preventDefault()//阻止預設行為
              //如果目前選中的是第一個,則下一個選中的是最後一個,否則是前一個
              nextIndex = index === 0 ? length - 1 : index - 1
              break
            case EVENT_CODE.right:
            case EVENT_CODE.down:
              //➡️⬇️ 選中後一個
              e.stopPropagation()
              e.preventDefault()
              //如果目前選中的是最後一個,則下一個選中的是第一個,否則是後一個
              nextIndex = (index === (length - 1)) ? 0 : index + 1
              break
            default:
              break
          }
          if (nextIndex === null) return
          roleRadios[nextIndex].click()
          roleRadios[nextIndex].focus()
        }
               

radio-button

  1. label

    放在最外層的作用是

    擴大滑鼠點選範圍

    ,無論是點選在文字還是input上都能夠觸發響
  2. 同上
    .el-radio-button__orig-radio {
        opacity: 0;
        outline: none;
        position: absolute;
        z-index: -1;
    }
               

繼續閱讀