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. [TJOI2017]DNA

    嘟嘟嘟 这题怎么想都想不出来,最后还是敲了暴力,喜提40分-- 正解竟然也是暴力-- 用\(s_0\)构造SAM,然后把\(s\)扔上去暴力dfs:记录一个修改次数tot,如果当前不匹配,就tot + ...

  2. 转://Oracle A用户给B用户授权查询指定表或视图权限方案

    用DNINMSV31账户登录数据库进行如下操作: CREATE USER NORTHBOUND IDENTIFIED BY NORTHBOUND DEFAULT TABLESPACE "TB ...

  3. 7-EL表达式和JSTL表达式

    引入jar包 一.EL表达式1.表达式语言,用于jsp网页中获取和计算数据2.语法:${表达式}3.用于取值:可以从pageContext,request,session,application这些域 ...

  4. ORA-28040: No matching authentication protocol

    1.2  前言部分 1.2.1  导读和注意事项 各位技术爱好者,看完本文后,你可以掌握如下的技能,也可以学到一些其它你所不知道的知识,~O(∩_∩)O~: ① 告警日志中频繁出现Using depr ...

  5. Ubuntu下的 PPPoE 拨号上网方法

    1. 配置 pppoe $ sudo pppoeconf 2. 联网 $ sudo pon dsl-provider 3. 断网 $ sudo poff 4. 查看日志 $ plog 5. 查看接口信 ...

  6. 从code review到Git commit log

    最近在读一本技术类的书:朱赟——<跃迁:从技术到管理的硅谷路径>,其中聊了很多很有趣的观点,比如:技术管理.技术实践.硅谷文化.个人成长等. 读到关于硅谷人如何做code review这一 ...

  7. day94

    基于CoentOS 7.4系统的Liunx操作 1.常用软件安装 yum install -y bash-completion vim lrzsz wget expect net-tools nc n ...

  8. 自定义实现moveable button

    实现的效果图: 自定义MVButton,继承自UIButton. 属性声明如下: @property (nonatomic) CGPoint beginPoint; @property (nonato ...

  9. CentOS7 安装MySQL5.6

    1.  检查是否有MariaDB和MySQL,如果有则卸载掉 [root@--- ~]# rpm -qa | egrep "mariadb|mysql" mariadb-serve ...

  10. ASP.NET Core 发布之后通过命令控制监听地址和环境变量

    添加Command支持 新建一个ASP.NET Core 项目,打开Program.cs 添加下面的代码: public class Program { public static void Main ...