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按钮,通过样式隐藏了。
第二个表示文字部分。 默认渲染间的文本;

(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);
      });

element-ui 源码解析 一的更多相关文章

  1. element ui源码解析 -- input篇

    el-input是element ui中使用最频繁的组件之一了,分析其构成从四个方面入手:DOM结构,属性,样式,事件入手 DOM结构: <div> <input /> < ...

  2. element ui源码解析 -- button篇

    要看源码就得从最简单的开始,button够简单的了,就从他开始吧. 安装依赖后源码目录在:node_modules/element-ui/packages中,可以看到这里的文件夹命名是不是很熟悉,就是 ...

  3. Element UI 源码—— Carousel 走马灯学习

    参考博客:https://segmentfault.com/a/1190000014384638?utm_source=tag-newest

  4. vue UI库iview源码解析(2)

    上篇问题 在上篇<iview源码解析(1)>中的index.js 入口文件的源码中有一段代码有点疑惑: /** * 在浏览器环境下默认加载组件 */ // auto install if ...

  5. 【vuejs深入二】vue源码解析之一,基础源码结构和htmlParse解析器

    写在前面 一个好的架构需要经过血与火的历练,一个好的工程师需要经过无数项目的摧残. vuejs是一个优秀的前端mvvm框架,它的易用性和渐进式的理念可以使每一个前端开发人员感到舒服,感到easy.它内 ...

  6. 【原创】backbone1.1.0源码解析之View

    作为MVC框架,M(odel)  V(iew)  C(ontroler)之间的联系是必不可少的,今天要说的就是View(视图) 通常我们在写逻辑代码也好或者是在ui组件也好,都需要跟dom打交道,我们 ...

  7. 【转载】Xutils3源码解析

    Github源码地址:https://github.com/wyouflf/xUtils3 原文地址 :http://www.codekk.com/blogs/detail/54cfab086c476 ...

  8. 【原】Android热更新开源项目Tinker源码解析系列之一:Dex热更新

    [原]Android热更新开源项目Tinker源码解析系列之一:Dex热更新 Tinker是微信的第一个开源项目,主要用于安卓应用bug的热修复和功能的迭代. Tinker github地址:http ...

  9. jQuery2.x源码解析(缓存篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...

  10. EventBus3.0源码解析

    本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充 ...

随机推荐

  1. vue快速入门

    Vue.js是当下很火的一个JavaScript MVVM库,它是以数据驱动和组件化的思想构建的.相比于Angular.js,Vue.js提供了更加简洁.更易于理解的API,使得我们能够快速地上手并使 ...

  2. 为了下载和使用最新的git,只好安装了迅雷

    一  安装迅雷时总感觉它的图标像是灰鸽子,让人不放心. 从https://git-scm.com/downloads上找到64-bit Git for Windows Setup的地址: https: ...

  3. day 07 字符编码

    一:字符编码 1.字符编码 什么是字符编码:将人能识别的字符转换为计算机能识别的01二进制的过程就是字符编码,转换的规则就是字符编码表 常用的编码表:ASCII.GBK.Unicode.UTF-8 了 ...

  4. RabbitMq 6种使用模式

    RabbitMQ的5种模式与实例 1.1 简单模式Hello World 功能:一个生产者P发送消息到队列Q,一个消费者C接收 生产者实现思路: 创建连接工厂ConnectionFactory,设置服 ...

  5. docker 5 docker-阿里云加速配置

    阿里云加速配置 鉴于国内网络问题,后续拉取docker镜像十分缓慢,我们可以配置加速器来解决. 1.阿里云地址  https://dev.aliyun.com/ 如果你想注册,可以注册个账号. 不想注 ...

  6. tomcat 改端口 运维最最重要的就是有看日志的习惯

    tomcat一台机器上多实例更改端口需要改三个端口 改tomcat关闭端口 <Server port="9006" shutdown="SHUTDOWN" ...

  7. Python+Pycharm—学习1—封装&导入

    一.封装 目的: 写了一个复杂功能的实现,下次有相同的需求时可以直接导入这个包来使用.就可以达到复用的目的了.通俗的来讲就是打包.(以下用四则运算举例) 方案: 1.1.新建python工程,新建一个 ...

  8. unique

    作用: 元素去重,即“删除”序列中所有相邻的重复元素(只保留一个) (此处的删除,并不是真的删除,而是指重复元素的位置被不重复的元素占领了) (其实就是把多余的元素放到了最后面) 由于它“删除”的是相 ...

  9. face alignment[Ordinary Procrustes Analysis]

    人脸识别,大致可以分为以下四个步骤: 人脸检测:从图片中准确定位到人脸,并以矩形框将其裁剪出来: 人脸矫正(对齐): 检测到的人脸,可能角度不是很正,需要使其对齐,比如旋转,缩放: 特征提取:对矫正后 ...

  10. DOM(二)

    文档信息 document对象还有一些标准的Document对象所没有的属性: title属性:包含着<title>元素中的文本——显示在浏览器窗口的标题栏或标签页上,通过整个属性可以取得 ...