zoukankan      html  css  js  c++  java
  • element-ui button组件 radio组件源码分析整理笔记(一)

    Button组件

    button.vue

    <template>
      <button
        class="el-button"
        @click="handleClick"
        :disabled="buttonDisabled || loading"
        :autofocus="autofocus"
        :type="nativeType"
        :class="[
          type ? 'el-button--' + type : '',
          buttonSize ? 'el-button--' + buttonSize : '',
          {
            'is-disabled': buttonDisabled,
            'is-loading': loading,
            'is-plain': plain,
            'is-round': round,
            'is-circle': circle
          }
        ]"
        >
        <i class="el-icon-loading" v-if="loading"></i>
        <i :class="icon" v-if="icon && !loading"></i>
        <span v-if="$slots.default"><slot></slot></span>
      </button>
    </template>
    <script>
      export default {
        name: 'ElButton',
          //provider/inject:简单的来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量
          inject: {
          elForm: {
            default: ''
          },
          elFormItem: {
            default: ''
          }
        },
    
        props: {
          type: {  //按钮类型
            type: String,
            default: 'default'
          },
          size: String, //按钮尺寸
          icon: { //图标类名
            type: String,
            default: ''
          },
          nativeType: { //原生type类型
            type: String,
            default: 'button'
          },
          loading: Boolean,//是否加载中状态
          disabled: Boolean,//是否禁用状态
          plain: Boolean, //是否朴素按钮
          autofocus: Boolean, //是否默认聚焦
          round: Boolean,//是否圆角按钮
          circle: Boolean //是否圆形按钮
        },
    
        computed: {
          //父组件中的变量
          _elFormItemSize() {
            return (this.elFormItem || {}).elFormItemSize;
          },
          //按钮尺寸计算
          buttonSize() {
            return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
          },
          //按钮是否禁用
          buttonDisabled() {
            return this.disabled || (this.elForm || {}).disabled;
          }
        },
    
        methods: {
          handleClick(evt) {
            this.$emit('click', evt);
          }
        }
      };
    </script>
    
    

    Radio组件

    radio.vue

    <template>
      <label
        class="el-radio"
        :class="[
          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
            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>
        <span class="el-radio__label" @keydown.stop>
          <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() {
            let parent = this.$parent;
            while (parent) {
              if (parent.$options.componentName !== 'ElRadioGroup') {
                parent = parent.$parent;
              } else {
                this._radioGroup = parent;
                return true;
              }
            }
            return false;
          },
          model: {
            get() {
              return this.isGroup ? this._radioGroup.value : this.value;
            },
            set(val) {
              if (this.isGroup) {
                this.dispatch('ElRadioGroup', 'input', [val]);
              } else {
                this.$emit('input', val);
              }
            }
          },
          _elFormItemSize() {
            return (this.elFormItem || {}).elFormItemSize;
          },
          radioSize() {
            const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
            return this.isGroup
              ? this._radioGroup.radioGroupSize || temRadioSize
              : temRadioSize;
          },
          isDisabled() {
            return this.isGroup
              ? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
              : this.disabled || (this.elForm || {}).disabled;
          },
          tabIndex() {
            return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
          }
        },
    
        methods: {
          handleChange() {
            this.$nextTick(() => {
              this.$emit('change', this.model);
              this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
            });
          }
        }
      };
    </script>
    
    

    解析:

    (1)组件的html结构

    <label ...>
        <span class='el-radio__input'>
            <span class='el-radio__inner'></span>
            <input type='radio' .../>
        </span>
        <span class='el-radio__label'>
            <slot></slot>
            <template v-if="!$slots.default">{{label}}</template>
        </span>
    </label>
    

    整个组件是一个外层label嵌套两个span。label放在最外面的作用是扩大鼠标点击范围,点击文字或者input都能触发响应。
    第一个 表示圆形按钮,里面的span就是模拟的圆圈,input才是真正的radio按钮,通过样式隐藏了。
    第二个表示文字部分。 默认渲染间的文本; 表示如果没有文本,但是标签,最后label的值会渲染出来。

    这篇博文中有详细的说明:https://juejin.im/post/5b7a9d61e51d4538e5679462 强烈推荐!

    (2)label部分

    <label
        class="el-radio"
        :class="[
          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"
      >
    

    1、 border && radioSize ? 'el-radio--' + radioSize : '',
    radioSize 是Radio 的尺寸,仅在 border 为真时有效。radioSize是计算属性

     radioSize() {
      // props 的 size 及 form 注入的 size 及全局配置对象 ($ELEMENT, 此对象由引入时 Vue.use() 传入的默认空对象)的 size
            const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
      // 通过isGroup判断是否被 radio-group 组件包裹,如果包裹优先 radioGroupSize
            return this.isGroup
              ? this._radioGroup.radioGroupSize || temRadioSize
              : temRadioSize;
          }
    

    isGroup:获取当前组件的父级组件,然后检查其组件名是否是ElRadioGroup即单选框组,如果不是就继续检查父级的父级。这个方法会找到距离自己最近的父级ElRadioGroup组件,有则true,无则false

    isGroup() {
            let parent = this.$parent;
            while (parent) {
              if (parent.$options.componentName !== 'ElRadioGroup') {
                parent = parent.$parent;
              } else {
                this._radioGroup = parent;
                return true;
              }
            }
            return false;
     }
    

    2、{ 'is-disabled': isDisabled } 表示是否禁用radio组件,与尺寸类似,就不说了。
    3、 { 'is-checked': model === label } 表示当前按钮是否被选中,通过 model === label 控制。这里看下model:

      model: {
            get() { //取值
               //通过isGroup判断是否被 radio-group 组件包裹,如果是就返回 radio-group的value:如果不是就返回自己的value
              return this.isGroup ? this._radioGroup.value : this.value;
            },
            set(val) { //赋值
              if (this.isGroup) {
             // 被 radio-group 组件包裹 ,radio-group 组件发布 input 事件,传递值
                this.dispatch('ElRadioGroup', 'input', [val]);
              } else {
             // 没有被 radio-group 组件包裹, 触发父组件上的input事件传递出val
                this.$emit('input', val);
              }
            }
          }
    

    4、WAI-ARIA无障碍网页应用属性

    role="radio"
    :aria-checked="model === label"
    :aria-disabled="isDisabled"
    

    WAI-ARIA指无障碍网页应用。主要针对的是视觉缺陷,失聪,行动不便的残疾人以及假装残疾的测试人员。尤其像盲人,眼睛看不到,其浏览网页则需要借助辅助设备,如屏幕阅读器,屏幕阅读机可以大声朗读或者输出盲文。而ARIA就是可以让屏幕阅读器准确识别网页中的内容,变化,状态的技术规范,可以让盲人这类用户也能无障碍阅读!

    5、:tabindex="tabIndex" :控制tab是否可以选中

      tabIndex() { 
            //禁用状态或者单选按钮是在单选框组组件内且不是选中状态时,返回 -1; 
            return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
     }
    

    当tabIndex=0时,该元素可以用tab键获取焦点,且访问顺序是按照元素在文档中的顺序来获取焦点;当tabIndex=1时,该元素用tab键获取不到焦点;当 tabindex > 0 的元素都切换之后,才会切换到 tabindex = 0 的元素,并且按出现的先后次序进行切换,这里的逻辑就是tab只能访问到选中状态下的单选按钮

    6、@keydown.space.stop.prevent="model = isDisabled ? model : label"
    空格键按下的时候给model赋值,但是不知道有什么用,不知道在什么场景下用得到。

    (3)混入选项

    import Emitter from 'element-ui/src/mixins/emitter';
    mixins: [Emitter],
    

    这里主要是用到了dispatch方法

    dispatch(componentName, eventName, params) {
          var parent = this.$parent || this.$root;
          var name = parent.$options.componentName;
          while (parent && (!name || name !== componentName)) {
            parent = parent.$parent;
            if (parent) {
              name = parent.$options.componentName;
            }
          }
          if (parent) {
            //必须用apply定$emit的调用目标对象,因为是在父组件上触发该事件而不是在dispatch里
            parent.$emit.apply(parent, [eventName].concat(params));
          }
        },
    

    第一个参数是父级组件的名称,第二个是事件名称,第三个参数是一个数组或者单独的值,逻辑也很简单:不断地取到自己的父组件,判断是否是目标组件,如果不是继续去其父组件判断,如果是则在父组件上调用$emit触发事件。

     handleChange() {
            this.$nextTick(() => {
              this.$emit('change', this.model);
                //找到父组件去触发父组件的handleChange事件
              this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
            });
    }
    
    this.$on('handleChange', value => {
            //在radio-group去触发父组件的change事件
            this.$emit('change', value);
          });
    

    参考博文:
    1、无障碍阅读:https://www.zhangxinxu.com/wordpress/2012/03/wai-aria-无障碍阅读/
    2、Element源码分析系列4-Radio(单选框):https://juejin.im/post/5b7a9d61e51d4538e5679462
    3、拜读及分析 Element 源码 - radio 组件篇:http://www.h3399.cn/201809/614690.html

  • 相关阅读:
    前端基础(2)CSS
    前端基础(1)、HTML
    MySQL数据库,这一篇就够啦!!!(持续更新)
    十、数据库之流程控制
    九、数据库之存储过程和函数
    spring注解总结
    eclipse导入项目后错误的处理方式
    ssm分页查询错误
    字节编址和字的区别(转)
    数据库查询练习
  • 原文地址:https://www.cnblogs.com/fangnianqin/p/9990511.html
Copyright © 2011-2022 走看看