美文网首页Vue
深入Vue之解析ElementUI组件--radio

深入Vue之解析ElementUI组件--radio

作者: Lia代码猪崽 | 来源:发表于2019-10-08 16:46 被阅读0次

    一、github地址

    https://github.com/ElemeFE/element/blob/dev/packages/radio/src/radio.vue

    二、文档地址

    https://element.eleme.cn/#/zh-CN/component/radio

    三、解析过程(很多注释在源代码是不合法的,这里只是为了更直观的展示)

    <template>
      <label
        class="el-radio"
        :class="[  // 注解1
          border && radioSize ? 'el-radio--' + radioSize : '',
          { 'is-disabled': isDisabled },
          { 'is-focus': focus },
          { 'is-bordered': border },
          { 'is-checked': model === label }
        ]"
        role="radio"  // 注解2
        :aria-checked="model === label"  // 注解2
        :aria-disabled="isDisabled"  // 注解2
        :tabindex="tabIndex"  // 注解2
        @keydown.space.stop.prevent="model = isDisabled ? model : label"  // 注解3
      >
        <span class="el-radio__input"  // 注解4
          :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"  // 注解1.5
            @focus="focus = true"
            @blur="focus = false"
            @change="handleChange"  // 注解5
            :name="name"
            :disabled="isDisabled"
            tabindex="-1"
          >
        </span>
        <span class="el-radio__label" @keydown.stop>  // 注解6
          <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() {  // 注解1.1
            let parent = this.$parent;
            while (parent) {
              if (parent.$options.componentName !== 'ElRadioGroup') {
                parent = parent.$parent;
              } else {
                this._radioGroup = parent;
                return true;
              }
            }
            return false;
          },
          model: {  // 注解1.5
            get() {
              return this.isGroup ? this._radioGroup.value : this.value;
            },
            set(val) {
              if (this.isGroup) {
                this.dispatch('ElRadioGroup', 'input', [val]);
              } else {
                this.$emit('input', val);
              }
              this.$refs.radio && (this.$refs.radio.checked = this.model === this.label);
            }
          },
          _elFormItemSize() {  // 注解1.1
            return (this.elFormItem || {}).elFormItemSize;
          },
          radioSize() {  // 注解1.1
            const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
            return this.isGroup
              ? this._radioGroup.radioGroupSize || temRadioSize
              : temRadioSize;
          },
          isDisabled() {  // 注解1.2
            return this.isGroup
              ? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
              : this.disabled || (this.elForm || {}).disabled;
          },
          tabIndex() {  // 注解2
            return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
          }
        },
        methods: {
          handleChange() { // 注解5
            this.$nextTick(() => {
              this.$emit('change', this.model);  // 注解1.5
              this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
            });
          }
        }
      };
    </script>
    
    1. 动态class样式
    1. 调用组件时候有没有传来border,且radioSize的返回值是否为true,则有类'el-radio--' + radioSize
     // 有this.elFormItem,则返回this.elFormItem.elFormItemSize;否则返回{}.elFormItemSize,即返回undefined
    _elFormItemSize() {
      return (this.elFormItem || {}).elFormItemSize; 
    },
    
    // 判断是否为radio-group
    isGroup() {
        let parent = this.$parent;  // 获取父节点的DOM元素
        while (parent) {
            if (parent.$options.componentName !== 'ElRadioGroup') {
                parent = parent.$parent;  // 改变parent,退出while循环,然后返回false
            } else {
                this._radioGroup = parent;
                return true;  // 已找到radio-group父节点,赋值给了this._radioGroup,返回true,代表是在group里
            }
        }
        return false;
    },
    
     radioSize() {
      const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;  // 获取到size
      return this.isGroup
        ? this._radioGroup.radioGroupSize || temRadioSize
              : temRadioSize;    // 如果是在group里,则返回this._radioGroup.radioGroupSize || temRadioSize;否则返回temRadioSize
    },
    
    1. 判断isDisabled返回的值,为true则有类is-disabled
    { 'is-disabled': isDisabled },
    
    // 是否为radio-group,是则返回this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled;否则返回this.disabled || (this.elForm || {}).disabled
    isDisabled() {
        return this.isGroup 
          ? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
             : this.disabled || (this.elForm || {}).disabled;
    },
    
    1. focus是否为true,则有类is-focus
    { 'is-focus': focus },
    
    1. 调用组件时候有传来border,就有类is-bordered
    { 'is-bordered': border },
    
    1. model(下面会详讲)是否与label(调用时父组件传来的label值)相等,就有类is-checked
    { 'is-checked': model === label }
    

    model具体,有用到知识点computed的读取和设置

    model: {
            get() {
              return this.isGroup ? this._radioGroup.value : this.value;  // 获取:是否在radio-group里,是返回this._radioGroup.value;否则返回this.value
            },
            set(val) {  // 更新
              if (this.isGroup) {  // 如果在radio-group里,
                this.dispatch('ElRadioGroup', 'input', [val]);  // 下面详讲
              } else {
                this.$emit('input', val);  // 如果不是,则直接触发调用父组件input事件,传递val过去
              }
              this.$refs.radio && (this.$refs.radio.checked = this.model === this.label);  
            }
    },
    

    Vue组件通信 dispatch,查找所有父级,直到找到要找到的父组件,并在身上触发指定的事件。

    // dispatch(componentName, eventName, params) {}
    // @param { componentName } 组件名称
    // @param { eventName } 事件名
    // @param { params } 参数
    this.dispatch('ElRadioGroup', 'input', [val]);
    
    2. HTML5中的aria与role

    这些都是HTML5针对html tag增加的属性,一般是为不方便的人士提供的功能,比如屏幕阅读器。
    role的作用是描述一个非标准的tag的实际作用。比如用divbutton,那么设置divrole="button",辅助工具就可以认出这实际上是个button
    aria的意思是Accessible Rich Internet Applicationaria-*的作用就是描述这个tag在可视化的情境中的具体信息。

    role="radio"  // 这实际上是个单选radio
    :aria-checked="model === label"  // 当前是否被选中
    :aria-disabled="isDisabled"  // 当前是否被禁用
    

    2.aria-label只有加在可被tab到的元素上,读屏才会读出其中的内容。可令tabindex0可读,-1不可读:

    :tabindex="tabIndex"
    
    // 如果是禁用状态,返回 -1
    // 如果是在单选框组里(radio-group),且不是单选选中的,返回-1
    // 其余返回0
    tabIndex() {  
      return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
    }
    
    3. @keydown.space.stop.prevent
    • @keydown.space:键修饰符.键别名,即按下了键盘的空格键触发事件
    • .stop:停止冒泡
    • .prevent:阻止默认行为
    // 当按下键盘的空格键时,如果是禁用状态,不改变model的值;否则model为label的值;
    // 停止冒泡、阻止默认行为
    @keydown.space.stop.prevent="model = isDisabled ? model : label"  
    
    4. 单选框圆点

    这一部分的全部HTML代码如下,待会儿会进行拆解:

    <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"  // 注解1.5
            @focus="focus = true"
            @blur="focus = false"
            @change="handleChange"
            :name="name"
            :disabled="isDisabled"
            tabindex="-1"
          >
    </span>
    
    未选中
    选中
    1. 外层,定位居中,判断是否为禁用和选中
    <span class="el-radio__input" 
      :class="{
        'is-disabled': isDisabled,  // 如果为禁用状态则有'is-disabled'类样式
        'is-checked': model === label  // 如果为当前选中则有'is-checked'类样式
      }"
    >
    ...
    </span>
    
    .el-radio__input {
        white-space: nowrap;
        cursor: pointer;
        outline: none;
        display: inline-block;
        line-height: 1;
        position: relative;
        vertical-align: middle;
    }
    
    1. 内层1,实际上显示的样式
    <span class="el-radio__inner"></span>
    
    .el-radio__inner {
        border: 1px solid #dcdfe6;
        border-radius: 100%;
        width: 14px;
        height: 14px;
        background-color: #fff;
        position: relative;
        cursor: pointer;
        display: inline-block;
        box-sizing: border-box;
    }
    .el-radio__inner:after {
        width: 4px;
        height: 4px;
        border-radius: 100%;
        background-color: #fff;
        content: "";
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%,-50%) scale(0);
        transition: transform .15s ease-in;
    }
    
    /*禁用样式*/
    .el-radio__input.is-disabled .el-radio__inner {
        background-color: #f5f7fa;
        border-color: #e4e7ed;
        cursor: not-allowed;
    }
    /*选中样式*/
    .el-radio__input.is-checked .el-radio__inner {
        border-color: #409eff;
        background: #409eff;
    }
    .el-radio__input.is-checked .el-radio__inner:after {
        transform: translate(-50%,-50%) scale(1); 
    }
    
    1. 内层2,因为太丑被隐藏了,但是实际上有大大的作用
    <input
            ref="radio"  // 可通过this.$refs.radio获取到dom节点
            class="el-radio__original"  // 样式
            :value="label"  // 绑定值为父组件调用传来的label
            type="radio"  // 单选
            aria-hidden="true"  // 阅读器模式下隐藏
            v-model="model"  // 注解1.5
            @focus="focus = true" 
            @blur="focus = false"
            @change="handleChange"  // 注解5
            :name="name"  // 绑定值为父组件调用传来的name
            :disabled="isDisabled"  // 根据isDisabled的值来定义是否禁用
            tabindex="-1"  // 阅读器模式下隐藏
    >
    
    .el-radio__original {
        opacity: 0;    // 透明度为0,依旧要占个位
        outline: none;
        position: absolute;
        z-index: -1;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        margin: 0;
    }
    
    5. $nextTick
    handleChange() {
      this.$nextTick(() => {
        this.$emit('change', this.model);  // 触发父组件调用change方法,参数为this.model(注解1.5)的值
        this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);  // 如果是在单选框组里,调用this.dispatch('ElRadioGroup', 'handleChange', this.model)
      });
    }
    
    6. label
    <span class="el-radio__label" @keydown.stop>  // 注解6
      <slot></slot>  // 提供插槽
      <template v-if="!$slots.default">{{label}}</template> // 如果没有使用default插槽,显示调用时传进来的label值
    </span>
    

    相关文章

      网友评论

        本文标题:深入Vue之解析ElementUI组件--radio

        本文链接:https://www.haomeiwen.com/subject/sgzvpctx.html