分享Vue3组件库搭建(1):如何写好一个Button组件(附源码以及预览)

分享Vue3组件库搭建(1):如何写好一个Button组件(附源码以及预览)

前语

组件库已成为,前端每日最长打交道的东西。当前不管公司规模巨细,他必定都会运用一套组件库来建立自己的项目。

  • 小公司,他或许会挑选一套满意自我功能需求的第三方组件库。也有人挑选一套老练的第三方组件库,再针对个别组件进行二次封装,如常见的表格,表单;

  • 再大一点的公司,或许会挑选一套伟人肩膀上二次封装的内部组件库。表面上现已是自己公司的组件库,其实底层仍是内藏一个优质的第三方。

  • 而专业的互联网公司,会有一个专门的团队,来建立自己的内部组件库。满意自己一切的内需的一起,开源出来让整个社区进行认可。

那么,为什么一般只有”专业的互联网公司”,才会挑选自己建立内部的组件库呢?中小公司,建立的难点在哪里?笔者今日剖析一下,最简略的Button,需求考虑什么。

背景

本文运用运用 vue3 + vite + typescript,以移动端组件库为目标。

笔者将使用额外的时刻,共享整个组件库的建立。本文先共享一个最简略的Button。

下边是笔者文章对应开源项目的截图:

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

Button按钮事例:

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

需求剖析

此刻假如你不是一个技能(或许非前端人员),脑海中一个原生button控件,加个点击事情处理。

然后事情或许没有这么简略。咱们看看一个老练的第三方组件库,button究竟做了什么?

1)老练组件库参阅

咱们先看看其他相对老练的组件库,处理了什么。

Ant Design供给的API

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

Element供给的API

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

Vant供给的API

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

Varlet供给的API

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

2)承认需求目标

此刻,看到一个老练组件库的供给的Api, 咱们能够考虑一下咱们终究需求完成的一个Button, 笔者结合第三方组件库为参阅,以及日常工作所需,确定本章想要的Button:

    1. 支撑文本,图标等
    1. 支撑不同的巨细,小号,中号,大号等。
    1. 支撑不同的类型,主题按钮,危险按钮,警告按钮等
    1. 支撑加载作用
    1. 支撑通明的形式
    1. 支撑圆角
    1. 支撑禁用
    1. 支撑块级展现
    1. 支撑触发中状态
    1. 展现上的优化
    1. 点击事情,防重,支撑异步等

3)全体图例

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

4)相关技能与规范

由上述的需求承认,咱们从而剖析部分咱们运用咱们需求的技能与规范。

本文主要描绘button的构建,规范等后续会有专文汇总,敬请关注。

这儿简略提及button几个涉及的技能与规范:Scss, PostCss兼容处理, Bem规范,CSS原子类等

5)声明

本文已引导Button的建立为准,事例的原意是帮助咱们更好的理解。详细源码还需看github。

完成剖析

依据上边的需求,咱们先手写一个最简略的Button, 再依据需求逐步推进:

1)手写最简略的Button

<template>
 <button class="cb-button"><slot /></button>
</template>
<script lang="ts">
export default {
  name: "Button",
  setup() {
    return {
    }
  }
}
</script>
<style>
...省掉
</style>
  • 作用图

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

2)支撑图标

上述现已支撑插槽,理论上现已满意图标的显现。

但这样很简略造成图标的巨细不一致,位置不一致等问题。假如咱们由组件去操控常用的按钮内置图标的款式,这样会使项目愈加的规范。

<template>
 <div class="cb-button">
     <img v-if="icon" :class="cb-button__img" :src="icon" />
     <slot />
 </div>
</template>
<script lang="ts">
export default {
  name: "Button",
  props:{
      //嵌入图标
      icon: {
        type: String,
        default: ""
      },
  },
  setup() {
    return {
    }
  }
}
</script>
  • 作用图

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

3)支撑不同的字号

在项目的建立进程,展现上按钮有巨细之分现已是十分常见的需求。常见的项目一般有5号字体,一般定义为: smaller, small, normal, large, larger.

笔者这儿用small, normal, large为事例:

<template>
 <div :class="['cb-button', [`cb-button__size-${size}`]]">
     <img v-if="icon" :class="cb-button__img" :src="icon" />
     <slot />
 </div>
</template>
<script lang="ts">
import { PropType } from "vue"
export type SizeItem = "small" | "normal" | "large" 
export default {
  name: "Button",
  props:{
      ...,
        // 字体巨细
      size: {
        type: String as PropType<SizeItem>,
        default: "normal",
        validator: (str: string) => {
          return ["small", "normal", "large"].includes(str)
        }
      },
  },
  setup() {
    return {
    }
  }
}
</script>
<style lang="scss">
    $size-array: (
    key: "small",
    fontSize: 12,
    minWidth: 30,
    height: 20,
    borderRadius: 10,
    padding: "0 4px"
  ),
  (
    key: "normal",
    fontSize: 14,
    minWidth: 70,
    height: 30,
    borderRadius: 15,
    padding: "0 6px"
  ),
  (
    key: "large",
    fontSize: 16,
    minWidth: 80,
    height: 40,
    borderRadius: 20,
    padding: "0 8px"
  );
  @include b(button) {
      @for $i from 1 through length($size-array) {
          $item: nth($size-array, $i);
          @include e("size") {
            @include m(map-get($item, key)) {
              font-size: #{map-get($item, fontSize)}px;
              min-width: #{map-get($item, minWidth)}px;
              height: #{map-get($item, height)}px;
              line-height: #{map-get($item, height)}px;
              border-radius: #{map-get($item, borderRadius)}px;
              padding: #{map-get($item, padding)};
              flex: 0 0 auto;
            }
          }
        }
  }
</style>
  • 作用图

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

4)支撑不同的主题

同理,按钮也需求支撑不同的主题: “primary” , “info” , “danger” , “warning ” , “link”

<template>
 <div :class="['cb-button', [`cb-button__type-${type}`]]">
     <img v-if="icon" :class="cb-button__img" :src="icon" />
     <slot />
 </div>
</template>
<script lang="ts">
import { PropType } from "vue"
export type ThemeType = "primary" | "info" | "danger" | "warning " | "link"
export default {
  name: "Button",
  props:{
      ...,
      // 类型
      type: {
        type: String as PropType<ThemeType>,
        default: "primary",
        validator: (str: string) => {
          return ["primary", "info", "danger", "warning", "link"].includes(str)
        }
      },
  },
  setup() {
    return {
    }
  }
}
</script>
<style lang="scss">
  ...后续款式省掉,详细看下方源码。
</style>

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

5)支撑加载作用

点击加载作用。这儿经过异步使命Promise去操控是否加载状态愈加合理,不过有些场景确实需求页面用loading去操控,笔者这儿先暴露为api供页面操控:

<template>
 <div :class="['cb-button', ...]">
    <span v-if="loading" :class="`${be}loading`" />
    <img v-else-if="icon" :class="`${be}img`" :src="icon" />
    <slot />
 </div>
</template>
<script lang="ts">
...
export default {
  name: "Button",
  props:{
      ...,
      // 是否加载状态
     loading: {
         type: Boolean,
         default: false
     },
  },
}
</script>
<style lang="scss">
  ...后续款式省掉,详细看下方源码。
</style>
  • 作用图

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

6)支撑通明形式

通明形式,也是现在的UI常用的规范,跟主题常规形式混搭,能够达到更抱负的展现作用。

源码跟”主题形式”差不多,这儿不再重复。需求看github、

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

7)支撑圆角设置

圆角假如大局一致,应该经过config文件进行操控。但是假如个别圆角特殊,咱们也需求支撑自定义:

<template>
  <div
    :class="[...]"
    :style="[baseStyles]"
  >
    <span v-if="loading" :class="`${be}loading`" />
    <img v-else-if="icon" :class="`${be}img`" :src="icon" />
    <slot></slot>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, toRefs } from "vue"
import { useCommon } from "../common/hooks/index"
import { props } from "./props"
// 按钮
export default defineComponent({
  // 按钮组件,用于事情触发
  name: "Button",
  props,
  setup(props, { emit }) {
    const baseStyles = ref(props.radius !== -1 ? { borderRadius: props.radius + "px" } : {})
    return {
      ...,
      baseStyles,
    }
  }
})
</script>

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

8)支撑禁用

禁用形式,源码跟”主题形式”差不多,这儿不再重复。需求看github。

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

9)支撑块级展现

块级形式。同理,实践中,咱们还需求占满整个快。源码跟”loading”差不多,需求看github。

共享Vue3组件库建立(1):怎么写好一个Button组件(附源码以及预览)

10)支撑点击(click)事情

假如咱们不处理click,页面也会触发。假设不声明 emits: [“click”], 页面还会触发两次。

那么是否要包装click呢?

答案是必定的。否则loading,disabled, 防重复点击等,怎么处理?

笔者这儿先出个初版, 且处理防重,详细看github源码:

<template>
  <div
    ...
    @click="onClick"
  >
    ...
    <slot></slot>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, toRefs } from "vue"
import { useCommon } from "../common/hooks/index"
import { props } from "./props"
// 按钮
export default defineComponent({
  // 按钮组件,用于事情触发
  name: "Button",
  props:{
      ...,
      onClick: {
        type: Function as PropType<(e: Event) => void | Promise<any>>,
      },
  },
  setup(props, { emit }) {
    const pending: Ref<boolean> = ref(false)
    const onClick = e => {
      const { loading, disabled, onClick } = props
      if (!onClick || loading || disabled || pending.value) {
        return
      }
      props.onClick(e)
      pending.value = false;
    }
    return {
      ...,
      baseStyles,
    }
  }
})
</script>

11)支撑触发(touchstart)事情

touchstart事情仍是有一定场景的。这儿简略遍及一下跟click的区别:

  • touchstart: 手指触碰开始就能触发

  • click: 1.手指触碰
    2.手指未在屏幕上移动
    3.在这个dom上手指脱离屏幕
    4.触摸和脱离屏幕之间的时刻距离较短

详细代码完成可参阅click。

12)事情是否支撑冒泡

常用的事情,都不支撑冒泡。但是有一些特殊的需求,如区域埋点等,他需求冒泡。咱们也把该事情暴露

    porps:{
          ...,
          //是否支撑冒泡
          isStopPropagation: {
            type: Boolean,
            default: true
          },
    }
    const onClick = e => {
      const { loading, disabled, onClick } = props
      if (!onClick || loading || disabled || pending.value) {
        return
      }
      props.onClick(e)
      pending.value = false;
      if (props.isStopPropagation) {
        e.stopPropagation()
      }
    }

13)支撑异步loading

常见等待后台执行异步使命,或许等待前端处理,需求一定的等待时刻方可二次触发。此刻咱们用autoLoading一个特点支撑:

    porps:{
          ...,
         // 自动loading形式
          autoLoading: {
            type: Boolean,
            default: false
          },
    }
    // setup:
    ...
    const loading = ref(props.loading)
    const onClick = async e => {
      if (loading.value) {
        return
      }
      if (props.autoLoading) {
        loading.value = true
        await props.onClick(e)
        loading.value = false
        // 最长防重复点击
        setTimeout(() => {
          if (loading.value) {
            loading.value = false
          }
        }, 5000)
      } else {
        props.onClick(e)
      }
      if (props.isStopPropagation) {
        e.stopPropagation()
      }
    }

14)参数操控

一个开放的组件库,毫无疑问款式上,色彩上,永远无法满意一切的业务场景。此刻就需求经过装备一些参数,来操控按钮的展现。

如:主题色怎么操控等。这儿后续再供给专文解说。

15)其他款式作用完成

此刻,你会发现按钮体会上还缺了一点什么?

是的。例如hover作用,active作用等,鼠标聚集事情等还未处理。还要进行额外的细节优化。这儿不再描绘。有兴趣看源码

源码链接

github: github.com/zhuangweizh…

gitee: 后续留意置顶评论。

预览:zhuangweizhan.github.io/cb-ui/dist/…

结语

看到这儿,手写button。还觉得是一个十分简略事情么?

假如文章对你有帮忙,欢迎继续关注,下一篇:怎么写好一个Input组件。