前言

Vue的世界中已经充满了各种各样的UI库了,尤其出名的有ElementUI、Iview、MuseUI等,这几个也是我们日常业务开发中经常使用的UI库。只使用UI库的工程师被调侃为api调用师,这算是一种工程师之间的鄙视了,这种鄙视迟早要还回去的。

最近在家比较闲,尝试着以成熟UI库的视角来感受下其的开发过程,看了这些优秀的代码,真的觉得自己的代码有点屎味了!不多说了,让我们开始打造自己的UI库吧,首先看下按钮是怎么开发出来的。

按钮的使用

在正式开发按钮之前,我们先看下按钮是如何使用的,以及有哪些主要属性或者功能。这对接下来的api定义是很有用处的,意义重大。

按钮的主要属性有这么几点:

  • 是否圆角
  • 是否有边框
  • 是否可点击(disabled)
  • 颜色
  • 大小
  • ...

其实还有更多的属性,这里我们不赘述了,知道了某些属性的开发过程,其他的也只是照葫芦画瓢的;再来看下真实场景中是如何使用的:

<xch-button color="primary" :disabled="true" :noBorder="true" circle size="m">按钮</xch-button>
复制代码

开发前的目录规划

清晰的目录规划,能为后面的代码带来意想不到的可读性。一个项目随着时间的推移,人员的变动,往往会变得越来越大,越来越难维护。能在前期就规划好一切当然是最好的,但是又有几人能做到呢。看下我们这个按钮组件的目录:

本项目使用的是vue-cli 4.0脚手架生成的,和以前版本生成的目录大致相同。我们编写的代码基本都在src目录下,其中components里放的是我们的.vue文件,style中放的是样式文件,本项目我们采用的是less,和其他几个css预处理器都差不多。更具体的代码逻辑,在下文会依次详细解说。

props 的定义

本文的最低要求是你已经具备了vue的基础知识。在开发一个组件之前,我们最好的做法是规划好这个组件的能力和相关的属性以及api。特别是api,轻易不要乱改,任何不兼容的修改都是不成熟的标志,对组件的使用者来说也是不负责任的表现,这也是为什么我们总是选择成熟优秀框架的原因,没有人希望隔三差五去修改不兼容的代码。回归正题,我们首先定义下我们需要的props:

props: {
    circle: Boolean, // 圆角
    noBorder: Boolean, // 边框
    disabled: Boolean, // 可点击
    color: String, // 颜色
    textColor: String, // 文本颜色
    size: {
        type: String,
        validator(value) {
            return ["l", "m", "s", "xs"].indexOf(value) != -1;
        }
    } // 按钮大小
}
复制代码

先说一下思想:这些传入的props,多数是为了控制某个样式的class是否需要应用,比如,circle传入的话,就代表着应用class: btn-circlesize传入'l'的话,就代表着应用class: btn-l。其他的属性也都大致一样。

样式的计算属性

上文也讲了,props的传入,其实多数是为了控制某个样式是否应用。我们使用vue的对象语法<div v-bind:class="{ active: isActive }"></div>来定义一个计算属性buttonCls控制着样式class这一块。

computed: {
    buttonCls() {
        return {
            [`${prefix}`]: true,
            [`${prefix}-circle`]: !!this.circle,
            [`${prefix}-no-border`]: this.noBorder === true,
            [`${prefix}-${this.color}`]: !!this.color,
            [`${prefix}-text-${this.textColor}`]: !!this.textColor,
            [`${prefix}-${this.size}`]: !!this.size
        };
    }
}
复制代码

整齐划一的代码真好看,其中变量是prefix是自己定义的前缀,这样能很好的区分其他UI库,ElementUI是以el-开头,Iview是以i-开头。这么一操作,立刻高大上起来了。不愧是皇家按钮, 哈哈。

template

template部分相对简单些,主要是将props和计算属性应用到button标签上:

<template>
  <button
    type="button"
    :class="buttonCls"
    :disabled="!!this.disabled"
    @click="click"
  >
    <slot></slot>
  </button>
</template>
复制代码

重头戏:样式

写到这里,逻辑部分基本写完了,一共50行不到的代码,虽然没有ElementUI的el-button的功能齐全,但也算是麻雀虽小五脏俱全吧,有兴趣的同学可以仿照el-button进一步扩展。下面我们重点学习下怎么规划和书写整洁的less代码(这部分代码真的惊艳到我了,我之前一直不重视css的代码)

在动手写代码之前,我们先梳理下有哪些要点:

  • 全局的样式抽离成单独的变量文件,这里就是var.less,这个习惯一定要养成,在编写大型的项目或者插件库的时候,面对堆积成山的代码量,会复用代码的人是快乐的,剩下的就是和稀泥的人,每天痛苦不堪。
  • 每个组件都有自己的less文件,这里是button.less
  • less语法是很强大的,平时用的最多的就是嵌套语法和变量模式,其实less还有很多的强大的功能,比如函数,混合等,都是很值得我们去好好理解和学习的

我们先看下var.less文件,里面我们定义了很多变量,比如,颜色、padding等,不仅是button组件会用到,以后的其他组件也会用到,先大概看下:

@prefix: xch-;

// 颜色
@primary-color : #45b984;
@blue-color : #3B91FF; //info
@green-color : #13CE66; //success
@yellow-color : #FFAE00; //warn
@red-color : #E11617; //error
@white-color : #fff;

//Dark, Gray 1-4 more
@dark-color: #333333;
@dark1-color: #555555;
@dark2-color: #666666;
@dark3-color: #777777;
@dark4-color: #999999;

@gray-color: #c1c1c1;
@gray1-color: #d3d3d3;
@gray2-color: #eeeeee;
@gray3-color: #f3f3f3;
@gray4-color: #f5f5f5;

// 按钮padding
@button-size-normal-padding: 8px 15px;
@button-size-l-padding: 10px 20px;
@button-size-m-padding: 7px 16px;
@button-size-s-padding: 5px 10px;
@button-size-xs-padding: 2px 6px;

// radius
@border-radius : 4px;

//font-size
@font-size : 14px;

// Animation
@animation-time : .3s;
@transition-time : .2s;

@box-shadow-button: 0 1px 1px 0 @gray2-color;

//disabled
@disabled-cursor: not-allowed;
@disabled-color: @dark4-color;
@disabled-border-color: @gray1-color;
@disabled-background-color: @gray4-color;
复制代码

button.less的代码,我使用了注释的方法,尽量详细的解说其中的奥妙

@btn-prefix: ~"@{prefix}btn"; // 定义前缀,这是UI组件库通用的做法

// 定义一个函数,方便控制颜色的切换
// 其中的darken和lighten方法是less自带的,变浅或者变深,甚是好用
.btn-color(@color, @percent: 10%) {
    background-color: @color;
    border-color: darken(@color, @percent);
    color: @white-color;
    &:hover{
        border-color: lighten(@color, @percent);
        background-color: lighten(@color, @percent);
    }
    &:active {
        border-color: darken(@color, @percent);
        background-color: darken(@color, @percent);
    }
}

.@{btn-prefix} {
    border: none;
    outline: none;
    padding: @button-size-normal-padding;
    display: inline-block;
    border-radius: @border-radius;
    font-size: @font-size;
    ...
    // 颜色切换的精髓
    &.@{btn-prefix} {
        &-primary{
            .btn-color(@primary-color)
        }
        &-red {
            .btn-color(@red-color);
        }
        ...
        
        &-no-border{
            box-shadow: none;
            border-color: transparent !important;
        }
        &-circle {
            border-radius: 20px;
        }
    }
    // 不可点击时的样式
    &[disabled] {
        cursor: @disabled-cursor;
        background-color: @disabled-background-color;
        border-color: @disabled-border-color;
        color: @disabled-color;
        &:hover {
            background-color: @disabled-background-color;
            border-color: @disabled-border-color;
            color: @disabled-color;
        }
    }
    // 控制按钮大小,其实是控制padding
    &.@{btn-prefix}-l {
        padding: @button-size-l-padding;
    }
    &.@{btn-prefix}-m {
        padding: @button-size-m-padding;
    }
    ...
}
复制代码

全局注册和使用

最后就是将本组件注册到全局,这部分代码在入口文件main.js中,比较简单就不累述了;感兴趣的同学可以思考下怎么定义另一个组件button-group来完成按钮的组合使用。

源码

还有些细枝末叶的代码和具体的按钮效果,这里也没有提到,可以去源码查看更多详细的代码。或者直接下载到本地,跑起来,运行下: git clone https://github.com/xch1029/vue-study.git。附上一句心里话,自己去实现一些UI库级别的代码真的能提升很多