Element组件源码研究-Button

4,255 阅读3分钟

前言

为锻炼封装组件得能力,我打算研究Elment UI组件,学习人家的设计和代码。

本文的研究思路是通过阅读Element源码,然后自动动手一步一步编写组件,完善其对应功能。

第一个想研究的就是Button组件,是我们最常用也最熟悉的,对标官方文档中介绍的Button特性,我们一个个来实现它们。

准备工作

使用vue-cli新建一个空项目

vue create my-element-ui

在项目中的components文件夹下新建Button文件夹,再新建一个index.vue,写按钮组件的代码。在根目录下新建views文件夹,再新建ButtonShownPage文件夹,再新建index.vue,在此页面展示按钮组件。

在ButtonShownPage中我写下

<template>
    <el-button @click="handleClick">默认按钮</el-button>
</template>

<script>
import ElButton from '../../components/Button/index'

export default {
    name: 'ButtonShownPage',
    methods: {
        handleClick(evt) {
            console.log('handleClick', evt);
        }
    },
    components: {
        ElButton
    }
}
</script>

这样我们开始专心写Button的代码,使其有正确的呈现

编写基本的Button

经过简单的摸索,我在Button组件里写下如下代码:

<template>
    <button 
        class="el-button"
        @click="handleClick"
    >
        <span v-if="$slots.default"><slot></slot></span>
    </button>
</template>

<script>

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

<style>
.el-button {
    display: inline-block;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    background: #fff;
    border: 1px solid #dcdfe6;
    color: #606266;
    -webkit-appearance: none;
    text-align: center;
    box-sizing: border-box;
    outline: none;
    margin: 0;
    transition: .1s;
    font-weight: 500;
    -moz-user-select: none;
    -webkit-user-select: none;
    -ms-user-select: none;
    padding: 12px 20px;
    font-size: 14px;
    border-radius: 4px;
}

.el-button:focus, .el-button:hover {
    color: #409eff;
    border-color: #c6e2ff;
    background-color: #ecf5ff;
}

.el-button:active {
    color:#3a8ee6;
    border-color:#3a8ee6;
    outline:none
}
</style>

使用button标签作为组件的基础,编写包含默认、focus、hover、active几种状态下的样式。利用slot包含按钮内容,click事件暴露出去。最基本的按钮就有了。

增加类型

现在让我们的button支持type,分别是primary,success,info,warning,danger。

先在ButtonShownPage中写测试代码:

<el-button @click="handleClick">默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>

再在Button中写逻辑代码:

<template>
    <button 
        class="el-button"
        :class="[type ? 'el-button--' + type : '']"
        @click="handleClick"
    >
        <span v-if="$slots.default"><slot></slot></span>
    </button>
</template>

<script>

export default {
    name: 'Button',
    props: {
        type: {
            type: String,
            default: 'default',
        },
    },
    methods: {
        handleClick(evt) {
            this.$emit('click', evt);
        }
    }
}
</script>

加一个prop名为type,默认为default,根据传入的值,为button标签添加样式。如传入的是primary,那么button就有了.el-button--primary这个class,我们再添加样式:

.el-button--primary {
    color: #fff;
    background-color: #409eff;
    border-color: #409eff;
}

.el-button--primary:focus, .el-button--primary:hover {
    background: #66b1ff;
    border-color: #66b1ff;
    color: #fff;
}

功能完成,另外添加个这样的样式:

.el-button+.el-button {
    margin-left: 10px;
}

即可为挨着的按钮们增加一个间距,效果如图:

plain、round和circle

这三个同type如出一辙,都是通过添加class来控制样式

<template>
    <button 
        class="el-button"
        :class="[type ? 'el-button--' + type : '',
         {
            'is-plain': plain,
            'is-round': round,
            'is-circle': circle
        }]"
        @click="handleClick"
    >
        <span v-if="$slots.default"><slot></slot></span>
    </button>
</template>

这时在props里增加plain, round, circle属性。

props: {
        type: {
            type: String,
            default: 'default',
        },
        plain: Boolean,
        round: Boolean,
        circle: Boolean
    },

由于要引用样式太多了,原理又相同,我通过浏览器开发者工具直接将elementui的样式直接整个copy过来放在assets文件夹中。

这样,效果如下:

我们的按钮支持多种形态了。

这里我们将所有的elemnnt的样式都拷贝过来了,所以后续就不需要关心样式的编写,只关心标签和js的逻辑,即可实现相应功能,但可以在呈现结果中,利用浏览器工具继续研究样式的细节。

支持icon

新增一个Icon组件:

<template>
  <i :class="'el-icon-' + name"></i>
</template>

<script>
  export default {
    name: 'ElIcon',
    props: {
      name: String
    }
  };
</script>

将Icon组件引入Button内,

<template>
    <button 
        class="el-button"
        :class="[type ? 'el-button--' + type : '',
         {
            'is-plain': plain,
            'is-round': round,
            'is-circle': circle
        }]"
        @click="handleClick"
    >
        <el-i :class="icon" v-if="icon"></el-i>
        <span v-if="$slots.default"><slot></slot></span>
    </button>
</template>

这样就达到了效果:

禁用状态

  1. 给组件增加一个属性disabled
  2. 给button标签的属性disabled绑定组件disabled属性,确保点击事件不生效
  3. 在:class中加一条'is-disabled': disabled即可。

效果如下:

文字按钮,图标按钮

现在我们的组件已经支持这两种样式的按钮了。

按钮组

新增一个ButtonGroup组件,关键点还是在于控制样式

<template>
  <div class="el-button-group">
    <slot></slot>
  </div>
</template>
<script>
  export default {
    name: 'ElButtonGroup'
  };
</script>

使用如下:

<el-button-group>
    <el-button type="primary" icon="el-icon-arrow-left">上一页</el-button>
    <el-button type="primary">下一页<i class="el-icon-arrow-right el-icon--right"></i></el-button>
</el-button-group>

呈现效果如下:

加载中

  1. 给组件增加一个属性loading
  2. 给class增加一条'is-loading': loading, 把disabled变成:disabled="disabled || loading"
  3. 把 <el-i :class="icon" v-if="icon>变成
<el-i class="el-icon-loading" v-if="loading"></el-i>
<el-i :class="icon" v-if="icon && !loading"></el-i>

效果如下:

按钮大小

同type一个套路,加一个size属性,控制class。

总结

基本的按钮组件就研究到这。全部示例如下:

本文的所有代码已上传至码云:gitee.com/DaBuChen/my…

其他组件源码研究:

Element组件源码研究-Button

Element组件源码研究-Input输入框

Element组件源码研究-Layout,Link,Radio

Element组件源码研究-Checkbox多选框

Element组件源码研究-InputNumber 计数器

Element组件源码研究-Loading组件

Element组件源码研究-Message组件