element-plus 源码学习——radio

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才能达到目的,这是个需要注意的地方

  3. disabled

    样式上的disabled绑定在label 上,功能上的disabled绑定在input

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

    isGroupelForm的取值都是通过inject注入

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

    rolerole的作用是描述一个非标准的tag的实际作用。比如用div做button,那么设置div 的 role=“button”,辅助工具就可以认出这实际上是个button。

    aria-*的作用就是描述这个tag在可视化的情境中的具体信息

    此tag 是一个radiocheckeddisabled 的属性分别是什么

  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;
    }
    
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值