2020轮子燥起来-首篇

786 阅读19分钟

前言

😄大家过年好!现在已经进入21世纪20年代了,20后的出现对于92年还没女朋友的我刺激还是蛮大的,虽然还差点步入中年,但我们还是需要展望未来,期待着一份美好的爱情。这也是我的新年愿望,希望和我有同样境遇的小伙伴在新的一年都能找到自己理想的另一半儿👄。

最近这两年前端技术真的是千变万化,让人透不过气来,但总有那些牛X的大佬及时的把新技术总结分享出来供我们这些小白白吸收,虽然还是记不太住...😫

作为一名前端儿,对普遍在公司写业务是最基本不过的事情了,使用过的轮子无数,但可能是岁数大了,记性不是特别好,对使用过的轮子再次用到还是免不了各种google、度娘走一波,诶心累😥。很久以前我就非常羡慕这些轮子的制造者,在日常工作中若能够封装组件会更加使自己的技术得到提升,基础更加扎实,说白了,封装组件这个套路那绝对是百利而无一害😘。

这篇作为2020年的起始篇,让我们从头来封装几个组件,了解封装组件的思路,愿广大前端儿都跨过组件封装这个门槛儿,也是为想进大厂体验高逼格工作氛围的童鞋们做好准备🤪。

所需技术储备

万事开头难,好的开头才会顺利的走好每一步。所以,封装之前我们需要掌握一些前置知识点:

  • vue
  • sass
  • ElementUI
  • git

只需熟悉以上4点我们便可以踏上封装组件的旅程

ElementUI作为一款非常优秀的前端UI库,在平时工作中使用的也是较多的,而且,我觉得我的审美再牛X,也想不出来比人家还要好看的样式体验,无论是样式还是代码上,我都会向它靠拢。很明显,我就是研究人家的源码来学习-😥-只不过我会尽量把思路捋出来供哪些没思路的小伙伴儿有些帮助,同时也对我自己组件封装相关知识的巩固。对于有不对的地方望大佬们轻喷,多提意见,毕竟都是圈儿里的,说话要搂着点😭

大过年的,碎碎叨叨的也差不多了,接下来,进入主题吧--最常用的组件莫过于button了,就用它来作为新年第一道菜,看看怎么样把它一步步炒成美食的。

前边操作我就略过了哈:

  • vue-cli起个项目,同时配置sass用来写css
  • 删掉所有多余的文件
  • 新建components目录用来存放封装的组件
  • 新建views目录用来展示及使用封装的组件
  • 新建theme目录存放组件样式及公共样式文件
  • router.js中我们就正常配置路由即可,用来展示我们写过的每个组件

附加:prettier+eslint来规范代码的书写,香不香谁用谁知道😋

封装前的准备工作

  • components/button/button.vue 在此文件中来封装button
  • views/button/button-view.vue 此文件用来使用封装过的button展示在页面上
  • theme/common 中存放公共的样式,包括字体、公共样式变量、及icon样式
  • theme/components 中存放各个对应的组件样式
  • theme/index.scss 用来导入所有上面的样式文件,便与组件文件访问
  • router/index.js 中配置对应的页面路由
{
    path: "/button",
    name: "button",
    component: Button
 },

button.vue中写如一个原生button按钮,并引入到button-view.vue文件中

components/button.vue

<template>
  <button>按钮</button>
</template>
<script>
export default {
  name: "ZButton"
};
</script>
<style lang="scss" scoped></style>

views/button/button-view.vue

<template>
  <z-button></z-button>
</template>
<script>
import ZButton from "@/components/button/button";
export default {
  name: "ZButtonView",
  components: {
    ZButton
  }
};
</script>

到此,页面上会出现写的这个原生按钮。先不急,先来简单看下elemetnui中的源码结构:

可以看到,这个目录结构和我们的不一样,来看几个重要的地方:

  • example文件夹存放的是一些示例代码
  • src文件夹中存放的是一些指令工具方法
  • packages文件夹中存放的是所有组件的封装源码
  • packages/theme-chalk/src中存放的是所有样式相关文件
  • types文件夹存放的是对于typescript的声明文件

了解其文件结构即可,想看源码的同学可以自行去深入琢磨。这里并不是要做成多么大的一个ui组件库,目的是实现一些组件的封装,了解其套路。

而且,ui库之所以为ui库,就是因为它们的样式很漂亮,我们在使用时并不需要写过多的样式去重写,大部分情况下直接使用即可。这就证明了ui库中的样式文件代码是很重要的,而且有一定的规范。 在封装组件之前我们先来梳理下样式相关的问题:

  • theme/common/vars.scss中先定义公共的样式变量
  • theme/common/font.scss中定义字体相关样式
  • theme/common/icon.scss中定义图标相关样式

因为样式代码太多了,这里就不贴出来了。我所用的都是elementui中的样式,只不过自己改动了些,没有人家那样规范。毕竟水平有限,css也是一门很大的学问,想要玩的精我感觉还是要下一番苦功夫的。

分析button组件

首先,我们先来看看人家封装的button组件具有哪些特性:

  • 根据传入的type值显示不同样式的按钮:defaultprimaryinfowarningsuccessdanger
  • 根据传入的size值显示不同尺寸大小的按钮
  • 根据传入不同的属性展示对应形状及状态的按钮:plainroundcircledisableloading
  • 根据传入native-type属性支持原生功能的按钮:buttonresetsubmit
  • 给按钮组件添加click事件,并触发父级组件的click事件,执行对应的业务逻辑
  • ButtonGroup按钮组

下面,我们开始逐一实现elementUIbutton的相关功能。

根据type显示不同样式的按钮

每套ui库使用时都会有一套自己的前缀,例如elementui中的el,我这参照人家来做,前缀定义为z

思路:根据传入不同的type来显示不同的样式的按钮,所以这里要把这个样式做成动态的,在vue中做成动态的可以使用对象、数组的方式,并且这些值需要传递进来,涉及到父子组件传值的一些操作。

实现:

components/button.vue

<template>
  <button
  class="z-button"
  :class="[  // 动态绑定class
      `z-button-- + ${type}`, //  重点在这里,type是父组件传递过来的
    ]"
  >
    <span>  // 该slot插槽用来显示传入的文本内容,以及后面要实现的右侧字体图标
      <slot></slot>
    </span>
  </button>
</template>
<script>
export default {
  name: "ZButton",
  props: {
    type: {  // 这里接受父组件传递过来的type属性,用来拼接动态class,生成不同样式的按钮
      type: String,
      default: "default"
    }
  }
};
</script>
<style lang="scss" scoped>
@import "../../theme/components/button.scss"; // 组件样式在这里写的,由于代码过多,就不展示了
</style>

其中还包括按钮的hoveractive等效果的样式代码

views/button/button-view.vue

<template>
  <div class="z-button-container">
    <div class="z-button-wrap">
      <div class="z-row">
        <z-button>不同样式的按钮</z-button>
        <z-button type="primary">主要按钮</z-button>
        <z-button type="success">成功按钮</z-button>
        <z-button type="info">信息按钮</z-button>
        <z-button type="warning">警告按钮</z-button>
        <z-button type="danger">危险按钮</z-button>
      </div>
    </div>
 </div>
</template>
<script>
import ZButton from "@/components/button/button";
export default {
  name: "ZButtonView",
  components: {
    ZButton
  }
};
</script>
 <style lang="scss" scoped>
 // 这里的css代码只是为了布局展示美观一些,跟封装组件的样式没啥关系
.z-button-wrap .z-row {
  margin-bottom: 20px;
}
.z-button-wrap .z-button + .z-button {
  margin-left: 10px;
}
.z-button-wrap .z-button-group + .z-button-group {
  margin-left: 10px;
}
.z-button-wrap .z-button-group .z-button {
  margin-left: 0;
}
.z-button-wrap .z-button-group {
  margin-bottom: 10px;
}
</style>

此时,在页面上会看到根据传入的不同的type,会展示不同样式的按钮了

根据传入的size值显示不同尺寸大小的按钮

思路:其实和上面定义不同样式的路子是一样的,只不过这次是根据传入的size改变尺寸而已。我们可以把传入的size作为计算属性来实现绑定不同的样式,同时可以要求开发人员在使用的时候只能传入mediumsmallmini这几个值,如果传入别的值进行报错用来提示开发人员 实现:

components/button.vue

<template>
  <button
  class="z-button"
  :class="[  // 动态绑定class
      `z-button-- + ${type}`, //  重点在这里,type是父组件传递过来的
      buttonSizeClass // 使用计算属性
    ]"
  >
    <span>  // 该slot插槽用来显示传入的文本内容,以及后面要实现的右侧字体图标
      <slot></slot>
    </span>
  </button>
</template>
<script>
export default {
  name: "ZButton",
  props: {
    type: {  // 这里接受父组件传递过来的type属性,用来拼接动态class,生成不同样式的按钮
      type: String,
      default: "default"
    },
    size: { // 父组件传递过来的值,通过validator校验器来规定传入的值的范围
      type: String,
      validator: value => {
        return ["medium", "small", "mini"].indexOf(value) !== -1;
      }
    }
  },
  computed: {  
    buttonSizeClass() { // 该计算属性用来计算class样式
      if (this.size) {
        return "z-button--" + this.size;
      }
      return null;
    }
  },
};
</script>
<style lang="scss" scoped>
@import "../../theme/components/button.scss"; // 组件样式在这里写的,由于代码过多,就不展示了,按钮尺寸相关样式也已添加在此文件中
</style>
views/button/button-view.vue

<template>
  <div class="z-button-container">
    <div class="z-button-wrap">
     <div class="z-row">
        <z-button>不同样式的按钮</z-button>
        <z-button type="primary">主要按钮</z-button>
        <z-button type="success">成功按钮</z-button>
        <z-button type="info">信息按钮</z-button>
        <z-button type="warning">警告按钮</z-button>
        <z-button type="danger">危险按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同尺寸的按钮</z-button>
        <z-button type="primary" size="medium">主要按钮</z-button>
        <z-button type="success" size="small">成功按钮</z-button>
        <z-button type="info" size="mini">信息按钮</z-button>
      </div>
    </div>
 </div>
</template>
<script>
import ZButton from "@/components/button/button";
export default {
  name: "ZButtonView",
  components: {
    ZButton
  }
};
</script>
 <style lang="scss" scoped>
 // 这里的css代码只是为了布局展示美观一些,跟封装组件的样式没啥关系
.z-button-wrap .z-row {
  margin-bottom: 20px;
}
.z-button-wrap .z-button + .z-button {
  margin-left: 10px;
}
.z-button-wrap .z-button-group + .z-button-group {
  margin-left: 10px;
}
.z-button-wrap .z-button-group .z-button {
  margin-left: 0;
}
.z-button-wrap .z-button-group {
  margin-bottom: 10px;
}
</style>

按钮形状

思路:elementUI中分为朴素、圆角、圆形按钮,可以根据父组件传入的布尔值来判断该按钮组件加入不同的样式类来实现。 实现:

components/button.vue

<template>
  <button
    class="z-button"
    :class="[ // 动态绑定class
      `z-button-- + ${type}`, //  重点在这里,type是父组件传递过来的
      buttonSizeClass, // 使用计算属性
      {            // 以对象的形式为按钮添加不同形状的样式类
        'is-plain': plain,
        'is-round': round,
        'is-circle': circle
      }
    ]"
  >
    <span>  // 该slot插槽用来显示传入的文本内容,以及后面要实现的右侧字体图标
      <slot></slot>
    </span>
  </button>
</template>
<script>
export default {
  name: "ZButton",
  props: {
    type: {  // 这里接受父组件传递过来的type属性,用来拼接动态class,生成不同样式的按钮
      type: String,
      default: "default"
    },
    size: { // 父组件传递过来的值,通过validator校验器来规定传入的值的范围
      type: String,
      validator: value => {
        return ["medium", "small", "mini"].indexOf(value) !== -1;
      }
    },
    // 以下三个布尔值来作为样式的标志状态,默认为false,为true则添加对应的样式类
    // 样式代码已在button.scss中写好
    plain: {
      type: Boolean,
      default: false
    },
    round: {
      type: Boolean,
      default: false
    },
    circle: {
      type: Boolean,
      default: false
    }
  },
  computed: {  
    buttonSizeClass() { // 该计算属性用来计算class样式
      if (this.size) {
        return "z-button--" + this.size;
      }
      return null;
    }
  },
};
</script>
<style lang="scss" scoped>
@import "../../theme/components/button.scss"; // 组件样式在这里写的,由于代码过多,就不展示了,按钮尺寸相关样式也已添加在此文件中
</style>
views/button/button-view.vue

<template>
  <div class="z-button-container">
    <div class="z-button-wrap">
      <div class="z-row">
        <z-button>不同样式的按钮</z-button>
        <z-button type="primary">主要按钮</z-button>
        <z-button type="success">成功按钮</z-button>
        <z-button type="info">信息按钮</z-button>
        <z-button type="warning">警告按钮</z-button>
        <z-button type="danger">危险按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同尺寸的按钮</z-button>
        <z-button type="primary" size="medium">主要按钮</z-button>
        <z-button type="success" size="small">成功按钮</z-button>
        <z-button type="info" size="mini">信息按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同形状的按钮</z-button>
        <z-button type="primary" plain>主要按钮</z-button>
        <z-button type="success" circle>成功按钮</z-button>
        <z-button type="info" round>信息按钮</z-button>
      </div>
    </div>
 </div>
</template>
<script>
import ZButton from "@/components/button/button";
export default {
  name: "ZButtonView",
  components: {
    ZButton
  }
};
</script>
 <style lang="scss" scoped>
 // 这里的css代码只是为了布局展示美观一些,跟封装组件的样式没啥关系
.z-button-wrap .z-row {
  margin-bottom: 20px;
}
.z-button-wrap .z-button + .z-button {
  margin-left: 10px;
}
.z-button-wrap .z-button-group + .z-button-group {
  margin-left: 10px;
}
.z-button-wrap .z-button-group .z-button {
  margin-left: 0;
}
.z-button-wrap .z-button-group {
  margin-bottom: 10px;
}
</style>

添加icon支持

思路:首先把elementui中的字体文件拷贝到咱们自己的src/assets目录中,在theme/common/font.scss中引入字体文件进行使用:

theme/common/font.scss

@font-face {
  font-family: "element-icons";
  src: url(../assets/element-icons.woff) format("woff"),
    url(../assets/element-icons.ttf) format("truetype");
  font-weight: normal;
  font-style: normal;
}
[class*=" z-icon-"],
[class^="z-icon-"] {
  font-family: element-icons !important;
  speak: none;
  font-style: normal;
  font-weight: 400;
  font-variant: normal;
  text-transform: none;
  line-height: 1;
  vertical-align: baseline;
  display: inline-block;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

theme/common/icon.scss中写入所有icon相关字体,及样式,这里我就简单写下,只包含了editloadingicon图标,其实可以把所有的图标都写在这里面:

theme/common/icon.scss

.z-icon-edit:before { 
  content: "\e78c";
}
.z-icon-loading:before {
  content: "\e6cf";
}
@keyframes rotating { // loading效果旋转动画
  0% {
    transform: rotateZ(0deg);
  }
  100% {
    transform: rotateZ(360deg);
  }
}
.z-icon-loading { // loading效果旋转动画
  animation: rotating 2s linear infinite;
}

接下来,实现icon的支持

components/button.vue

<template>
  <button
    class="z-button"
    :class="[ // 动态绑定class
      `z-button-- + ${type}`, //  重点在这里,type是父组件传递过来的
      buttonSizeClass, // 使用计算属性
      {            // 以对象的形式为按钮添加不同形状的样式类
        'is-plain': plain,
        'is-round': round,
        'is-circle': circle
      }
    ]"
  >
    <i v-if="icon" :class="icon"></i> // icon显示的位置
    <span v-if="$slots.default"> // 这里做个判断,只有插槽内有内容才展示,因为有图标按钮,没文字的那种
      <slot></slot> // 该slot插槽用来显示传入的文本内容,以及后面要实现的右侧字体图标
    </span>
  </button>
</template>
<script>
export default {
  name: "ZButton",
  props: {
    type: {  // 这里接受父组件传递过来的type属性,用来拼接动态class,生成不同样式的按钮
      type: String,
      default: "default"
    },
    size: { // 父组件传递过来的值,通过validator校验器来规定传入的值的范围
      type: String,
      validator: value => {
        return ["medium", "small", "mini"].indexOf(value) !== -1;
      }
    },
    // 以下三个布尔值来作为样式的标志状态,默认为false,为true则添加对应的样式类
    // 样式代码已在button.scss中写好
    plain: {
      type: Boolean,
      default: false
    },
    round: {
      type: Boolean,
      default: false
    },
    circle: {
      type: Boolean,
      default: false
    },
    icon: String // 接受父组件传递过来的icon值
  },
  computed: {  
    buttonSizeClass() { // 该计算属性用来计算class样式
      if (this.size) {
        return "z-button--" + this.size;
      }
      return null;
    }
  },
};
</script>
<style lang="scss" scoped>
@import "../../theme/components/button.scss"; // 组件样式在这里写的,由于代码过多,就不展示了,按钮尺寸相关样式也已添加在此文件中
</style>
views/button/button-view.vue

<template>
  <div class="z-button-container">
    <div class="z-button-wrap">
      <div class="z-row">
        <z-button>不同样式的按钮</z-button>
        <z-button type="primary">主要按钮</z-button>
        <z-button type="success">成功按钮</z-button>
        <z-button type="info">信息按钮</z-button>
        <z-button type="warning">警告按钮</z-button>
        <z-button type="danger">危险按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同尺寸的按钮</z-button>
        <z-button type="primary" size="medium">主要按钮</z-button>
        <z-button type="success" size="small">成功按钮</z-button>
        <z-button type="info" size="mini">信息按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同形状的按钮</z-button>
        <z-button type="primary" plain>主要按钮</z-button>
        <z-button type="success" circle>成功按钮</z-button>
        <z-button type="info" round>信息按钮</z-button>
      </div>
      <div class="z-row">
        <z-button plain>默认按钮</z-button>
        <z-button type="primary" :loading="loading" plain icon="z-icon-edit">
          主要按钮
          <i class="z-icon-edit z-icon--right"></i> // 子组件的插槽存放右侧字体图标作用体现在这里
        </z-button>
      </div>
    </div>
 </div>
</template>
<script>
import ZButton from "@/components/button/button";
export default {
  name: "ZButtonView",
  components: {
    ZButton
  }
};
</script>
 <style lang="scss" scoped>
 // 这里的css代码只是为了布局展示美观一些,跟封装组件的样式没啥关系
.z-button-wrap .z-row {
  margin-bottom: 20px;
}
.z-button-wrap .z-button + .z-button {
  margin-left: 10px;
}
.z-button-wrap .z-button-group + .z-button-group {
  margin-left: 10px;
}
.z-button-wrap .z-button-group .z-button {
  margin-left: 0;
}
.z-button-wrap .z-button-group {
  margin-bottom: 10px;
}
</style>

loadingdisabled

思路:定义好loading状态的样式,根据传入的布尔值来决定是否是loadingdisabled状态。并且因为loading的时候也是不可点击的,即禁用状态,样式不同,但功能类似。所以我们可以把这两个属性写在一起。还有一点就是字体图标按钮如果显示loading状态,那么该字体图标就不要显示了。 实现:

components/button.vue

<template>
  <button
  :disabled="disable || loading"  // 用同一个属性实现禁用状态即可
  class="z-button"
  :class="[ // 动态绑定class
      `z-button-- + ${type}`, //  重点在这里,type是父组件传递过来的
      buttonSizeClass, // 使用计算属性
      {            // 以对象的形式为按钮添加不同形状的样式类
        'is-plain': plain,
        'is-round': round,
        'is-circle': circle
      }
    ]"
  >
    <i v-if="!loading && icon" :class="icon"></i> // 这里加入loading判断条件,loading为false图标才显示
    <i v-if="loading" class="z-icon-loading"></i> // loading图标,css其实就是让图标进行循环旋转
    <span v-if="$slots.default"> // 这里做个判断,只有插槽内有内容才展示,因为有图标按钮,没文字的那种
      <slot></slot> // 该slot插槽用来显示传入的文本内容,以及后面要实现的右侧字体图标
    </span>
  </button>
</template>
<script>
export default {
  name: "ZButton",
  props: {
    type: {  // 这里接受父组件传递过来的type属性,用来拼接动态class,生成不同样式的按钮
      type: String,
      default: "default"
    },
    size: { // 父组件传递过来的值,通过validator校验器来规定传入的值的范围
      type: String,
      validator: value => {
        return ["medium", "small", "mini"].indexOf(value) !== -1;
      }
    },
    // 以下三个布尔值来作为样式的标志状态,默认为false,为true则添加对应的样式类
    // 样式代码已在button.scss中写好
    plain: {
      type: Boolean,
      default: false
    },
    round: {
      type: Boolean,
      default: false
    },
    circle: {
      type: Boolean,
      default: false
    },
    disable: {
      type: Boolean,
      default: false
    },
    loading: Boolean
  },
  computed: {  
    buttonSizeClass() { // 该计算属性用来计算class样式
      if (this.size) {
        return "z-button--" + this.size;
      }
      return null;
    }
  },
};
</script>
<style lang="scss" scoped>
@import "../../theme/components/button.scss"; // 组件样式在这里写的,由于代码过多,就不展示了,按钮尺寸相关样式也已添加在此文件中
</style>
views/button/button-view.vue

<template>
  <div class="z-button-container">
    <div class="z-button-wrap">
      <div class="z-row">
        <z-button>不同样式的按钮</z-button>
        <z-button type="primary">主要按钮</z-button>
        <z-button type="success">成功按钮</z-button>
        <z-button type="info">信息按钮</z-button>
        <z-button type="warning">警告按钮</z-button>
        <z-button type="danger">危险按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同尺寸的按钮</z-button>
        <z-button type="primary" size="medium">主要按钮</z-button>
        <z-button type="success" size="small">成功按钮</z-button>
        <z-button type="info" size="mini">信息按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同形状的按钮</z-button>
        <z-button type="primary" plain>主要按钮</z-button>
        <z-button type="success" circle>成功按钮</z-button>
        <z-button type="info" round>信息按钮</z-button>
      </div>
       <div class="z-row">
        <z-button>禁用及loading按钮</z-button>
        <z-button type="primary" plain disabled>主要按钮</z-button>
        <z-button type="success" circle loading>成功按钮</z-button>
      </div>
    </div>
 </div>
</template>
<script>
import ZButton from "@/components/button/button";
export default {
  name: "ZButtonView",
  components: {
    ZButton
  }
};
</script>
 <style lang="scss" scoped>
 // 这里的css代码只是为了布局展示美观一些,跟封装组件的样式没啥关系
.z-button-wrap .z-row {
  margin-bottom: 20px;
}
.z-button-wrap .z-button + .z-button {
  margin-left: 10px;
}
.z-button-wrap .z-button-group + .z-button-group {
  margin-left: 10px;
}
.z-button-wrap .z-button-group .z-button {
  margin-left: 0;
}
.z-button-wrap .z-button-group {
  margin-bottom: 10px;
}
</style>

给按钮添加click事件

思路:给按钮绑定click事件,都是用来处理对应的业务逻辑,所以要把事件往外传递,在父组件中去处理业务逻辑。 实现:

components/button.vue

<template>
  <button
  :disabled="disable || loading"  // 用同一个属性实现禁用状态即可
  class="z-button"
  :class="[ // 动态绑定class
      `z-button-- + ${type}`, //  重点在这里,type是父组件传递过来的
      buttonSizeClass, // 使用计算属性
      {            // 以对象的形式为按钮添加不同形状的样式类
        'is-plain': plain,
        'is-round': round,
        'is-circle': circle
      }
    ]"
    @click="handleClick" // 给按钮绑定click事件
  >
    <i v-if="!loading && icon" :class="icon"></i> // 这里加入loading判断条件,loading为false图标才显示
    <i v-if="loading" class="z-icon-loading"></i> // loading图标,css其实就是让图标进行循环旋转
    <span v-if="$slots.default"> // 这里做个判断,只有插槽内有内容才展示,因为有图标按钮,没文字的那种
      <slot></slot> // 该slot插槽用来显示传入的文本内容,以及后面要实现的右侧字体图标
    </span>
  </button>
</template>
<script>
export default {
  name: "ZButton",
  props: {
    type: {  // 这里接受父组件传递过来的type属性,用来拼接动态class,生成不同样式的按钮
      type: String,
      default: "default"
    },
    size: { // 父组件传递过来的值,通过validator校验器来规定传入的值的范围
      type: String,
      validator: value => {
        return ["medium", "small", "mini"].indexOf(value) !== -1;
      }
    },
    // 以下三个布尔值来作为样式的标志状态,默认为false,为true则添加对应的样式类
    // 样式代码已在button.scss中写好
    plain: {
      type: Boolean,
      default: false
    },
    round: {
      type: Boolean,
      default: false
    },
    circle: {
      type: Boolean,
      default: false
    },
    disable: {
      type: Boolean,
      default: false
    },
    loading: Boolean
  },
  computed: {  
    buttonSizeClass() { // 该计算属性用来计算class样式
      if (this.size) {
        return "z-button--" + this.size;
      }
      return null;
    }
  },
  methods: {
    handleClick(event) { // 触发父级click事件,去处理对应的业务逻辑
      this.$emit("click", event);
    }
  }
};
</script>
<style lang="scss" scoped>
@import "../../theme/components/button.scss"; // 组件样式在这里写的,由于代码过多,就不展示了,按钮尺寸相关样式也已添加在此文件中
</style>
views/button/button-view.vue

<template>
  <div class="z-button-container">
    <div class="z-button-wrap">
      <div class="z-row">
        <z-button>不同样式的按钮</z-button>
        <z-button type="primary">主要按钮</z-button>
        <z-button type="success">成功按钮</z-button>
        <z-button type="info">信息按钮</z-button>
        <z-button type="warning">警告按钮</z-button>
        <z-button type="danger">危险按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同尺寸的按钮</z-button>
        <z-button type="primary" size="medium">主要按钮</z-button>
        <z-button type="success" size="small">成功按钮</z-button>
        <z-button type="info" size="mini">信息按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同形状的按钮</z-button>
        <z-button type="primary" plain>主要按钮</z-button>
        <z-button type="success" circle>成功按钮</z-button>
        <z-button type="info" round>信息按钮</z-button>
      </div>
       <div class="z-row">
        <z-button>禁用及loading按钮</z-button>
        <z-button type="primary" plain disabled>主要按钮</z-button>
        <z-button type="success" circle loading>成功按钮</z-button>
      </div>
      <div class="z-row">
        <z-button @click="handleParentClick">带有click事件的按钮</z-button>
        <z-button :loading="loading">loading按钮</z-button>
      </div>
    </div>
 </div>
</template>
<script>
import ZButton from "@/components/button/button";
export default {
  name: "ZButtonView",
  data() {
    return {
      loading: false
    };
  },
  components: {
    ZButton
  },
  methods: {
    handleParentClick() {
      // 执行业务逻辑。比如发送ajax请求,开启Loading,请求结束关闭loading 
      this.loading = true
      // 发送请求
      setTimeout(()=>{
        this.loading = false  
      },2000)
    } // 此时点击按钮会触发loading效果,并在2s后关闭loading
  }
};
</script>
 <style lang="scss" scoped>
 // 这里的css代码只是为了布局展示美观一些,跟封装组件的样式没啥关系
.z-button-wrap .z-row {
  margin-bottom: 20px;
}
.z-button-wrap .z-button + .z-button {
  margin-left: 10px;
}
.z-button-wrap .z-button-group + .z-button-group {
  margin-left: 10px;
}
.z-button-wrap .z-button-group .z-button {
  margin-left: 0;
}
.z-button-wrap .z-button-group {
  margin-bottom: 10px;
}
</style>

让按钮组件支持原生方法

思路:动态绑定按钮的type属性,父组件传值,实现原生方法的功能。 实现:

components/button.vue

<template>
  <button
  :type="nativeType" // 动态绑定type属性
  :disabled="disable || loading"  // 用同一个属性实现禁用状态即可
  class="z-button"
  :class="[ // 动态绑定class
      `z-button-- + ${type}`, //  重点在这里,type是父组件传递过来的
      buttonSizeClass, // 使用计算属性
      {            // 以对象的形式为按钮添加不同形状的样式类
        'is-plain': plain,
        'is-round': round,
        'is-circle': circle
      }
    ]"
    @click="handleClick" // 给按钮绑定click事件
  >
    <i v-if="!loading && icon" :class="icon"></i> // 这里加入loading判断条件,loading为false图标才显示
    <i v-if="loading" class="z-icon-loading"></i> // loading图标,css其实就是让图标进行循环旋转
    <span v-if="$slots.default"> // 这里做个判断,只有插槽内有内容才展示,因为有图标按钮,没文字的那种 
      <slot></slot> // 该slot插槽用来显示传入的文本内容,以及后面要实现的右侧字体图标
    </span>
  </button>
</template>
<script>
export default {
  name: "ZButton",
  props: {
    type: {  // 这里接受父组件传递过来的type属性,用来拼接动态class,生成不同样式的按钮
      type: String,
      default: "default"
    },
    size: { // 父组件传递过来的值,通过validator校验器来规定传入的值的范围
      type: String,
      validator: value => {
        return ["medium", "small", "mini"].indexOf(value) !== -1;
      }
    },
    // 以下三个布尔值来作为样式的标志状态,默认为false,为true则添加对应的样式类
    // 样式代码已在button.scss中写好
    plain: {
      type: Boolean,
      default: false
    },
    round: {
      type: Boolean,
      default: false
    },
    circle: {
      type: Boolean,
      default: false
    },
    disable: {
      type: Boolean,
      default: false
    },
    loading: Boolean,
    nativeType: { // 获取父组件传递过来的type值,并用validator校验让使用者只能选择一下三种之一
      type: String,
      default: "button",
      validator: value => {
        return ["button", "reset", "submit"].indexOf(value) !== -1;
      }
    },
  },
  computed: {  
    buttonSizeClass() { // 该计算属性用来计算class样式
      if (this.size) {
        return "z-button--" + this.size;
      }
      return null;
    }
  },
  methods: {
    handleClick(event) { // 触发父级click事件,去处理对应的业务逻辑
      this.$emit("click", event);
    }
  }
};
</script>
<style lang="scss" scoped>
@import "../../theme/components/button.scss"; // 组件样式在这里写的,由于代码过多,就不展示了,按钮尺寸相关样式也已添加在此文件中
</style>
views/button/button-view.vue

<template>
  <div class="z-button-container">
    <div class="z-button-wrap">
      <div class="z-row">
        <z-button>不同样式的按钮</z-button>
        <z-button type="primary">主要按钮</z-button>
        <z-button type="success">成功按钮</z-button>
        <z-button type="info">信息按钮</z-button>
        <z-button type="warning">警告按钮</z-button>
        <z-button type="danger">危险按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同尺寸的按钮</z-button>
        <z-button type="primary" size="medium">主要按钮</z-button>
        <z-button type="success" size="small">成功按钮</z-button>
        <z-button type="info" size="mini">信息按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同形状的按钮</z-button>
        <z-button type="primary" plain>主要按钮</z-button>
        <z-button type="success" circle>成功按钮</z-button>
        <z-button type="info" round>信息按钮</z-button>
      </div>
       <div class="z-row">
        <z-button>禁用及loading按钮</z-button>
        <z-button type="primary" plain disabled>主要按钮</z-button>
        <z-button type="success" circle loading>成功按钮</z-button>
      </div>
      <div class="z-row">
        <z-button @click="handleParentClick">带有click事件的按钮</z-button>
        <z-button :loading="loading">loading按钮</z-button>
      </div>
      <div class="z-row">
        <z-button autofocus>支持原生事件按钮</z-button>
        <z-button native-type="submit">提交按钮</z-button>
        <z-button native-type="reset" type="primary">重置按钮</z-button>
      </div>
    </div>
 </div>
</template>
<script>
import ZButton from "@/components/button/button";
export default {
  name: "ZButtonView",
  data() {
    return {
      loading: false
    };
  },
  components: {
    ZButton
  },
  methods: {
    handleParentClick() {
      // 执行业务逻辑。比如发送ajax请求,开启Loading,请求结束关闭loading 
      this.loading = true
      // 发送请求
      setTimeout(()=>{
        this.loading = false  
      },2000)
    } // 此时点击按钮会触发loading效果,并在2s后关闭loading
  }
};
</script>
 <style lang="scss" scoped>
 // 这里的css代码只是为了布局展示美观一些,跟封装组件的样式没啥关系
.z-button-wrap .z-row {
  margin-bottom: 20px;
}
.z-button-wrap .z-button + .z-button {
  margin-left: 10px;
}
.z-button-wrap .z-button-group + .z-button-group {
  margin-left: 10px;
}
.z-button-wrap .z-button-group .z-button {
  margin-left: 0;
}
.z-button-wrap .z-button-group {
  margin-bottom: 10px;
}
</style>

实现按钮组

思路:按钮组的功能实际上就是在外层做曾包裹,把按钮组件放入其中,难点在于css的样式控制,两端的按钮有圆角,中间如果有多个按钮都没有圆角,用css的选择器想好逻辑做对应的处理即可。 实现: 这里新建一个文件: /components/button-group.vue

/components/button-group.vue
 
<template>
  <div class="z-button-group">
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: "ZButtonGroup"
};
</script>
<style lang="scss" scoped>
@import "../../theme/components/button-group.scss"; 
</style>

button-group的样式并不算多,我先展示在这里,这里需要和button的样式结合。

.z-button-group {
  display: inline-block;
  .z-button + .z-button {
    margin-left: 0;
  }
  .z-button:first-child:not(:last-child) {
    border-top-right-radius: 0;
    border-bottom-right-radius: 0;
  }
  .z-button:last-child:not(:first-child) {
    border-top-left-radius: 0;
    border-bottom-left-radius: 0;
  }
  .z-button:not(:first-child):not(:last-child) {
    border-top-left-radius: 0;
    border-top-right-radius: 0;
    border-bottom-left-radius: 0;
    border-bottom-right-radius: 0;
  }
  .z-button--primary:last-child:not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--primary:not(:last-child):not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--success:last-child:not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--success:not(:last-child):not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--info:last-child:not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--info:not(:last-child):not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--warning:last-child:not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--warning:not(:last-child):not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--danger:last-child:not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
  .z-button--danger:not(:last-child):not(:first-child) {
    border-left-color: rgba(255, 255, 255, 0.5);
  }
}
views/button/button-view.vue
<template>
  <div class="z-button-container">
    <div class="z-button-wrap">
      <div class="z-row">
        <z-button>不同样式的按钮</z-button>
        <z-button type="primary">主要按钮</z-button>
        <z-button type="success">成功按钮</z-button>
        <z-button type="info">信息按钮</z-button>
        <z-button type="warning">警告按钮</z-button>
        <z-button type="danger">危险按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同尺寸的按钮</z-button>
        <z-button type="primary" size="medium">主要按钮</z-button>
        <z-button type="success" size="small">成功按钮</z-button>
        <z-button type="info" size="mini">信息按钮</z-button>
      </div>
      <div class="z-row">
        <z-button>不同形状的按钮</z-button>
        <z-button type="primary" plain>主要按钮</z-button>
        <z-button type="success" circle>成功按钮</z-button>
        <z-button type="info" round>信息按钮</z-button>
      </div>
       <div class="z-row">
        <z-button>禁用及loading按钮</z-button>
        <z-button type="primary" plain disabled>主要按钮</z-button>
        <z-button type="success" circle loading>成功按钮</z-button>
      </div>
      <div class="z-row">
        <z-button @click="handleParentClick">带有click事件的按钮</z-button>
        <z-button :loading="loading">loading按钮</z-button>
      </div>
      <div class="z-row">
        <z-button autofocus>支持原生事件按钮</z-button>
        <z-button native-type="submit">提交按钮</z-button>
        <z-button native-type="reset" type="primary">重置按钮</z-button>
      </div>
      <div class="z-row">
        <z-button-group>
          <z-button type="warning" plain>按钮组</z-button>
          <z-button type="warning" plain>上一页</z-button>
          <z-button type="warning" plain>下一页</z-button>
          <z-button type="warning" plain>下一页</z-button>
        </z-button-group>
        <z-button-group>
          <z-button type="success" circle></z-button>
          <z-button type="success" circle></z-button>
          <z-button type="success" circle></z-button>
        </z-button-group>
      </div>
    </div>
 </div>
</template>
<script>
import ZButton from "@/components/button/button";
import ZButtonGroup from "@/components/button-group/button-group"; // 引入buttongroup
export default {
  name: "ZButtonView",
  data() {
    return {
      loading: false
    };
  },
  components: {
    ZButton,
    ZButtonGroup
  },
  methods: {
    handleParentClick() {
      // 执行业务逻辑。比如发送ajax请求,开启Loading,请求结束关闭loading 
      this.loading = true
      // 发送请求
      setTimeout(()=>{
        this.loading = false  
      },2000)
    } // 此时点击按钮会触发loading效果,并在2s后关闭loading
  }
};
</script>
 <style lang="scss" scoped>
 // 这里的css代码只是为了布局展示美观一些,跟封装组件的样式没啥关系
.z-button-wrap .z-row {
  margin-bottom: 20px;
}
.z-button-wrap .z-button + .z-button {
  margin-left: 10px;
}
.z-button-wrap .z-button-group + .z-button-group {
  margin-left: 10px;
}
.z-button-wrap .z-button-group .z-button {
  margin-left: 0;
}
.z-button-wrap .z-button-group {
  margin-bottom: 10px;
}
</style>

通过以上的代码,button组件的封装差不多都封装完毕了,后续整理一下再把这部分整体的代码贴出来,代码中主要的地方我加上了注释,希望对某些童鞋们能有个整体思路的了解。

今天也是年前在公司的最后一天了,这一年过得也是晕晕乎乎,好的一点是年会得了个二等奖,虽然不是啥贵重东西,起码也预示着新的一年会有个好运吧😜。接下来就是回家过年了😱,没女票都不知道该咋汇报,诶。年后有时间再继续搞一波别的组件,这东西挺费神的,归根结底自身能力还是有很大欠缺,还需继续努力。

2020新的愿望: 争取找到另一半儿,技术方面再升点段位🙃,也就这点追求了。