Element-UI radio、radio-group、radio-button 单选框源码

6 篇文章 0 订阅

Element-UI radio 单选框源码

radio模板部分:
<template>
  <label
  //label 元素不会向用户呈现任何特殊效果。不过,它为鼠标用户改进了可用性。如果您在 label 元素内点击文本,就会触发此控件。就是说,当用户选择该标签时,浏览器就会自动将焦点转到和标签相关的表单控件上。
    class="el-radio"
    :class="[
      border && radioSize ? 'el-radio--' + radioSize : '', //border 与 radioSize 同时存在则有该样式
      { 'is-disabled': isDisabled }, 	//是否禁用
      { 'is-focus': focus },			//是否选中
      { 'is-bordered': border },		//是否有边框
      { 'is-checked': model === label } // model计算属性见下面 script 
    ]"
    role="radio"	// role aria-checked aria-disabled 用于屏幕阅读器的,帮助残障人士更好的访问网站
    :aria-checked="model === label"
    :aria-disabled="isDisabled"
    :tabindex="tabIndex"  // 设置是否可以通过键盘上的 tab键 进行选择,  -1 代表不可选, 0 代表可选
    @keydown.space.stop.prevent="model = isDisabled ? model : label" 
    // keydown.space 空格  .stop 停止冒泡  .prevent 阻止默认行为   .stop.prevent 串联修饰符
    //当tab选中当前 radio 在键盘上敲击空格键的时候(space即空格键 ),阻止了原生事件发生
  >
    <span class="el-radio__input"
      :class="{
        'is-disabled': isDisabled,
        'is-checked': model === label
      }"
    >
      <span class="el-radio__inner"></span> // 采用 css 伪类实现选中效果,代替 input 
      <input
        ref="radio" // 注册 ref
        class="el-radio__original" //设置了 opacity: 0 对其隐藏
        :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>
        // 要是没有插槽内容则显示 label
      <template v-if="!$slots.default">{{label}}</template>
    </span>
  </label>
</template>

问题一:为啥不用原生 input 而是自己模拟

1、原生标签 radio不同浏览器样式不同,所以自己写来代替

2、需要用到原生的 radio来获取焦点来触发 change事件,所以 element是 采用了 绝对定位老脱离文档流,以及设置透明度为0,而不是采用 dispaly:none或者 visibility:hidden,因为这样就无法点击了

radioscript 部分:
export default {
    name: 'ElRadio', // name 三个好处 见其他源码文章

    mixins: [Emitter], // mixins 下文单独拎出来与之相关部分  也就是 自己实现 dispatch

    inject: {	//与 Form 表单相关,表单之内的不在本文说,包括下文的一些属性的判断
      elForm: {
        default: ''
      },

      elFormItem: {
        default: ''
      }
    },

    componentName: 'ElRadio', //用于当前 Vue 实例的初始化选项。需要在选项中包含自定义 property 时会有用处:
	/* 比如:
		new Vue({
          customOption: 'foo',
          created: function () {
            console.log(this.$options.customOption) // => 'foo'
          }
		})
	*/ 
    
    
    
    props: {	// 属性传值
      value: {},
      label: {},
      disabled: Boolean,
      name: String,
      border: Boolean,
      size: String
    },

    data() {
      return {
        focus: false
      };
    },
    computed: {
      isGroup() { // 是否为 radio-group 组件实例 ,是则为 true , 否则为 false
        let parent = this.$parent; 
        // 向上循环找它爸爸,一直找到为止 也就是 下面的 parent.$options.componentName === 'ElRadioGroup' 的时候
        while (parent) {
          if (parent.$options.componentName !== 'ElRadioGroup') {
            parent = parent.$parent;
          } else {
            this._radioGroup = parent; // 父实例,如果当前实例有的话
            return true;		
          }
        }
        return false;
      },
      model: { // 计算属性的 读取 get 和设置  set 
        get() { //如果是 radio-group 则取 radio-group 上面的值,否则则取 radio 本身的值
          return this.isGroup ? this._radioGroup.value : this.value;
        },
        set(val) {
          if (this.isGroup) {//自己实现 dispatch 事件 Emitter
           // 被radio-group组件包裹 radio-group组件发布input事件数组形式暴露值
            this.dispatch('ElRadioGroup', 'input', [val]);
          } else {
            this.$emit('input', val); //$emit 分发事件
          }
          this.$refs.radio && (this.$refs.radio.checked = this.model === this.label);
        }
      },
      _elFormItemSize() { // FormItem  
        return (this.elFormItem || {}).elFormItemSize;
      },
      radioSize() {//radio 尺寸
          // 自身尺寸优先级最高
        const temRadioSize = this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
          //如果为 radio-group 则取 radio-group 上面的尺寸,radio-group 没有设置大小则取自身
        return this.isGroup
          ? this._radioGroup.radioGroupSize || temRadioSize
          : temRadioSize;
      },
      isDisabled() {
          //判断是否为 radio-group 有则取 radio-group 的值 反之取 radio
        return this.isGroup
          ? this._radioGroup.disabled || this.disabled || (this.elForm || {}).disabled
          : this.disabled || (this.elForm || {}).disabled;
      },
      tabIndex() {
          //tabindex 属性规定元素的 tab 键控制次序(当 tab 键用于导航时)
        return (this.isDisabled || (this.isGroup && this.model !== this.label)) ? -1 : 0;
      }
    },

    methods: {
      handleChange() {
        this.$nextTick(() => {
            // 派发事件
          this.$emit('change', this.model);
            // 如果被radio-group组件嵌套,向上找到radio-group组件发布handleChange事件暴露model
          this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);
        });
      }
    }
  };


混入Emitter.js(这里只说和radio 相关的 dispatch)
Vue.mixin( mixin )全局注册一个混入,影响注册之后所有创建的每个 Vue 实例。
 import Emitter from 'element-ui/src/mixins/emitter';
 export default {
 	 mixins: [Emitter],
 }
 
 Emitter  中的 dispatch
 
 export default {
  methods: {
    dispatch(componentName, eventName, params) {
   	  // this.$root 当前组件树的根 Vue 实例。如果当前实例没有父实例,此实例将会是其自己。
      var parent = this.$parent || this.$root;
      //组件名
      var name = parent.$options.componentName;
	  // 当 parent 存在的时候, name 有值则走 name 是否等于 函数传递进来的 componentName 
      while (parent && (!name || name !== componentName)) {
        parent = parent.$parent;
		// 当 parent 存在的时候 ,name 则再去该 parent 上面的值
        if (parent) {
          name = parent.$options.componentName;
        }
      }
      // 当上面循环不满足时,及找到符合组件名称的父级后,发布其事件
      if (parent) {
        parent.$emit.apply(parent, [eventName].concat(params));
      }
    },
  }
};

调用方法: this.isGroup && this.dispatch('ElRadioGroup', 'handleChange', this.model);

必须用apply定$emit的调用目标对象,因为是在父组件上触发该事件而不是在dispatch里,这里你可能会说parent.$emit不就是在父组件上调用么?其实不是,parent.$emit仅仅是拿到了emit这个方法而已,并没有说明在哪里调用!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值