ElementUI源码分析二:button组件

328 阅读3分钟

“ 本文正在参加「金石计划 . 瓜分6万现金大奖」

上一篇:ElementUI源码分析一:整体目录分析

一 button概述

image.png button-group和index.js文件比较简单,这里重点我们分析button.vue文件

二 button源码分析

重点部分我会在代码中加上注释:

<template>
<!-- button的结构部分 可以看出element-ui的button按钮样式在原生的button上添加属性方法样式 -->
    <!-- 这里的class类没有直接写在此vue文件中,后面分析 -->
  <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>
    <!-- 默认文字/button中的文字 文本插槽 -->
    <span v-if="$slots.default"><slot></slot></span>
  </button>
</template>
<script>
  export default {
    name: 'ElButton',

    inject: {
      elForm: {
        default: ''
      },
      elFormItem: {
        default: ''
      }
    },

// props就是我们平时在业务中使用button时设置的参数,这里与官网的文档参数一一对应
    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.$options.propsData.hasOwnProperty('disabled') ? this.disabled : (this.elForm || {}).disabled;
      }
    },

    methods: {
      handleClick(evt) {
        this.$emit('click', evt);
      }
    }
  };
</script>

三 button样式代码分析

通过分析源码发现,样式代码并没有直接写在vue文件中,全局搜索后发现 package/theme-chalk中找到button按钮的样式文件 查看其他组件的vue文件发现也没有样式代码,所以element中的样式代码时单独放到这里的。 image.png 先从import的几个文件,挨个分析

var.scss

common/var.scss 公共变量文件源码 定义公共样式和所有组件样式变量的文件,例如主题颜色、字体颜色、字体大小等,可以通过这个文件实现组件库的换肤。 其中有一个写法特别感兴趣:

/* Color
-------------------------- */
/// color|1|Brand Color|0
$--color-primary: #409EFF !default;
/// color|1|Background Color|4
$--color-white: #FFFFFF !default;
/// color|1|Background Color|4
$--color-black: #000000 !default;
$--color-primary-light-1: mix($--color-white, $--color-primary, 10%) !default; /* 53a8ff */
$--color-primary-light-2: mix($--color-white, $--color-primary, 20%) !default; /* 66b1ff */
$--color-primary-light-3: mix($--color-white, $--color-primary, 30%) !default; /* 79bbff */
$--color-primary-light-4: mix($--color-white, $--color-primary, 40%) !default; /* 8cc5ff */
$--color-primary-light-5: mix($--color-white, $--color-primary, 50%) !default; /* a0cfff */
$--color-primary-light-6: mix($--color-white, $--color-primary, 60%) !default; /* b3d8ff */
$--color-primary-light-7: mix($--color-white, $--color-primary, 70%) !default; /* c6e2ff */
$--color-primary-light-8: mix($--color-white, $--color-primary, 80%) !default; /* d9ecff */
$--color-primary-light-9: mix($--color-white, $--color-primary, 90%) !default; /* ecf5ff */
/// color|1|Functional Color|1
$--color-success: #67C23A !default;
/// color|1|Functional Color|1
$--color-warning: #E6A23C !default;
/// color|1|Functional Color|1
$--color-danger: #F56C6C !default;
/// color|1|Functional Color|1
$--color-info: #909399 !default;

这种mix的样式写法是把两种颜色按照不同比例混合而成生成新的颜色,百分比则是占比,可以混合多种颜色 很有趣

mix详解MDN链接 其他的没什么好讲的,用的是scss语法,太久没用了很多属性忘记,留个坑后续把这块补上。

_button.scss

打开后发现主要封装了三种样式,那为什么不在button中直接写呢,然后继续找到其中一个方法全局查找 image.png 根据 button-size全局查找后得出一下结果 image.png 可以看出来由于这三个方法在很多地方用到,所以用也是单独拿出来写 在其他地方用到的写法:

  @include button-size($--button-padding-vertical, $--button-padding-horizontal, $--button-font-size, $--button-border-radius);

三个方法分别为:  button-plain:朴素样式 button-variant:不同类型的按钮样式 button-size:按钮尺寸样式 注:传入的参数$color是从var.scss中传入的。这也是可以全局换肤的原因 这里我就单独那应该出来解释,因为他们写法多差不多,挑最难的一个

<!-- 导入三个参数, -->
@mixin button-variant($color, $background-color, $border-color) {
  color: $color;
  background-color: $background-color;
  border-color: $border-color;

  &:hover,
  &:focus {
    // 使用mix混合颜色,上面讲过
    background: mix($--color-white, $background-color, $--button-hover-tint-percent);
    border-color: mix($--color-white, $border-color, $--button-hover-tint-percent);
    color: $color;
  }
  
  &:active {
    background: mix($--color-black, $background-color, $--button-active-shade-percent);
    border-color: mix($--color-black, $border-color, $--button-active-shade-percent);
    color: $color;
    outline: none;
  }

  &.is-active {
    background: mix($--color-black, $background-color, $--button-active-shade-percent);
    border-color: mix($--color-black, $border-color, $--button-active-shade-percent);
    color: $color;
  }

  &.is-disabled {
    &,
    &:hover,
    &:focus,
    &:active {
      color: $--color-white;
      background-color: mix($background-color, $--color-white);
      border-color: mix($border-color, $--color-white);
    }
  }
 // 背景使用plain(朴素)时的背景
  &.is-plain {
    @include button-plain($background-color);
  }
}

mixins.scss

这个文件夹,写的应该是一些公共样式,只能在后续的学习中遇到再具体分析,但是遇到几个不理解的点还是学习一下: 下面这段代码吸引了我的注意: 1 BEM是什么? 2 别的样式命名都是mixin+单词,这里使用字母 b e m是什么意思?难道因为简单吗?


/* BEM
 -------------------------- */
@mixin b($block) {
  $B: $namespace+'-'+$block !global;

  .#{$B} {
    @content;
  }
}

@mixin e($element) {
  $E: $element !global;
  $selector: &;
  $currentSelector: "";
  @each $unit in $element {
    $currentSelector: #{$currentSelector + "." + $B + $element-separator + $unit + ","};
  }

  @if hitAllSpecialNestRule($selector) {
    @at-root {
      #{$selector} {
        #{$currentSelector} {
          @content;
        }
      }
    }
  } @else {
    @at-root {
      #{$currentSelector} {
        @content;
      }
    }
  }
}

@mixin m($modifier) {
  $selector: &;
  $currentSelector: "";
  @each $unit in $modifier {
    $currentSelector: #{$currentSelector + & + $modifier-separator + $unit + ","};
  }

  @at-root {
    #{$currentSelector} {
      @content;
    }
  }
}

那么就来研究一手:

百度百科得知:

BEM是一种前端命名方法论,主要是针对CSS,意思是块(Block)、元素(Element)、修饰符(Modifier)的简写。这种命名方法让CSS便于统一团队开发规范和方便维护。

换句话说 b是block的写法,e是element m是modifiter , 通过多方操作后得到大佬解读:

2、e 是 element 的简写函数,@include e(icon) 调用,element传入icon,在上面b函数已经将element 传入 icon,在上面 b 函数已经将 B 赋值为全局变量 el-button,$currentSelector 拼接后得到 .el-button__icon,@at-root 是跳出嵌套,和 .el-button 同级,而不是 .el-button .el-button-icon 拼接在后面

3、m 修饰符函数,传入 primary, 遍历 modifier只有一个元素,遍历结果后modifier 只有一个元素,遍历结果后 currentSelector 赋值是 &--primary,在scss 编译, & 是上级类 el-button,编辑是 el-button--primary 举例: 在_mixins.scss中使用:

@mixin b($block) {
  $B: $namespace+'-'+$block !global;

  .#{$B} {
    @content;
  }
}

element大佬在源码中使用:

@include b(button) {
  display: inline-block;
  line-height: 1;
}

编译结果:

.el-button {
  display: inline-block;
  line-height: 1;
}

又学会了一点奇怪的知识!

utils.scss

这个文件和上面的几个差不多,也是一些公共样式,应该为了便于维护 image.png

四 button逻辑代码分析

  computed: {
      _elFormItemSize() {
        return (this.elFormItem || {}).elFormItemSize;
      },
      buttonSize() {
        return this.size || this._elFormItemSize || (this.$ELEMENT || {}).size;
      },
      buttonDisabled() {
        return this.$options.propsData.hasOwnProperty('disabled') ? this.disabled : (this.elForm || {}).disabled;
      }
    }

主要有三个计算属性

其实在button中的数据,有两个来源,一个是普通的prop引入的,这个比较好理解,参数都是依靠这个引入的,

另外一个就是computed计算属性了

这里就涉及到provider/inject,

provider/inject:简单的来说就是在父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。注意官方文档有一句话“向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效”,这样看来就明显了,父组件提供的属性只能在直接子组件中获取到,孙组件就获取不到,provide提供的属性就解决了这个问题。 为什么用到computed?答案就很明显了,防止全局设置/父组件的属性,孙组件获取不到

Vue.use(Element, {   size: Cookies.get('size') || 'medium' // set element-ui default size }) 

有了provider/inject,这里配置的size才能全局起作用。

在这里分别监听的button的大小,是否禁用,以及formItem的大小,从而做出改变。

至于methods中的cilck事件 就比较简单,

子组件向父组件通信,使用了$emit事件。

    methods: {
      handleClick(evt) {
        this.$emit('click', evt);
      }
    }

本篇着重对于button的一些样式代码进行了分析,因为逻辑部分确实比较简单,刚刚开始,后面会选择一些典型的组件,继续去阅读源码学习。

以上就是Button按钮的源码分析过程,刚刚开始学习,文章中若有不足或者错误的地方!欢迎指正