Vue3组件库搭建2-Button组件

131 阅读3分钟

封装Button组件

image.png 项目地址 gitee.com/fx666/fuxui…

在封装组件之前,首先需要对组件进行一个简单的分析:

  • 属性(props)
  • 事件
  • 插槽
  • 功能

可以进行一个逆向分析,想一下开发者在使用你这个组件的时候,是如何使用的。

例如,目前我们要封装的是 Button,封装完成后,开发者使用的示例如下:

<fux-button>按钮</fux-button>
<!-- 可以传递 type 属性 -->
<fux-button type="primary">按钮</fux-button>
<fux-button type="success">按钮</fux-button>
<!-- 可以传递 plain 属性 -->
<fux-button type="primary" plain>按钮</fux-button>
<!-- 可以传递 round 属性 -->
<fux-button type="primary" round>按钮</fux-button>
<fux-button type="primary" plain round>按钮</fux-button>
<!-- 可以传递 disabled 属性 -->
<fux-button type="primary" disabled>按钮</fux-button>
<!-- 可以传递 circle 属性 -->
<fux-button type="primary" circle>按钮</fux-button>
<!-- 可以传递 icon 属性 -->
<fux-button type="primary" circle icon="milk">按钮</fux-button>

props

用户如何使用组件考虑清楚后,这个组件的 props 属性也就出来:

参数名参数描述参数类型默认值
type按钮类型(primary/success/warning/danger/infostringdefault
plain是否是朴素按钮booleanfalse
round是否是圆角按钮booleanfalse
circle是否是圆形按钮booleanfalse
disabled是否禁用按钮booleanfalse
icon图标类名string

props 清晰了之后,接下来在 button 的 src 下面创建 button.ts 文件,该文件用于定义 button 组件的 props:

// 该文件用于定义 button 的 props 属性
// 将 props 定义为 button 的类型
// ExtractPropTypes 是 vue3 所提供的一个工具类型
// 用于从 vue 组件的 props 对象中提取 ts 类型
import type { ExtractPropTypes } from "vue";

// 定义 props
export const buttonProps = {
  type: {
    type: String,
    default: "default",
  },
  plain: {
    type: Boolean,
    default: false,
  },
  round: {
    type: Boolean,
    default: false,
  },
  circle: {
    type: Boolean,
    default: false,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  icon: {
    type: String,
    default: "",
  },
};

export type ButtonProps = ExtractPropTypes<typeof buttonProps>;

样式

关于 button 组件的封装,实际上主要的工作就是在样式上面。

关于样式我们有单独的一个包 theme-chalk,这个包来负责所有组件的样式。

该包所对应的结构如下:

  • theme-chalk
    • src
      • components:存放各个组件的样式
      • fonts:字体文件目录
      • mixins:存储混入的目录
        • config.scss:样式相关的配置
        • mixins.scss:存储各种 mixins
      • icon.scss:从 iconfont 上面下载的字体图标样式
      • index.scss:样式的入口文件
    • package.json

首先我们在 config.scss 文件中定义了命名空间:

// 该文件是样式相关的配置文件

// 定义一个命名空间
$namespace: 'fux'

接下来在 mixins.scss 中引入了配置,并且将这个配置导出:

// 该文件用于存储各种 mixin
@use "config" as *;
@forward "config"

在上面的代码中,用到 @use 以及 @forward,这两个指令都是在 scss 1.23.0 版本之后所提供了,之前使用的是 @import

  • @use: 该指令用于将所引入的文件中的变量、函数、混入等导入到当前的 scss 文件中。比如上面的代码,就是将 config.scss 文件中所有的变量、函数、混入导入到 mixins.scss 里面
  • @forward:负责将一个模块的变量、函数以及混入导出。

@use 的例子:

// config.scss
$color: red;
// a.scss
@use 'config' as *
body {
    background-color: $color;
}

@forward的例子:

// _buttons.scss
@mixin button-base(){}
// main.scss : 主scss文件
@forward 'buttons';
// 接下来就可以在其他 scss 文件中使用
@use 'main' as m;
.button{
  @include m.button-base();
}

在 theme-chalk/src/components 目录下,创建 button.scss,该文件用于编写 button 相关的样式,例如:

// button 组件所对应的样式
@use "../mixins/mixins.scss" as *;

// .fux-button { ...}
.#{$namespace}-button {
  display: inline-block;
  line-height: 1;
  white-space: nowrap;
  cursor: pointer;
  background: #ffffff;
  border: 1px solid #dcdfe6;
  color: hsl(220, 3%, 39%);
  appearance: none;
  text-align: center;
  box-sizing: border-box;
  outline: none;
  margin: 0;
  transition: 0.1s;
  font-weight: 500;
  //禁止元素的文字被选中
  user-select: none;
  padding: 12px 20px;
  font-size: 14px;
  border-radius: 4px;
  &:hover,
  &:hover {
    color: #409eff;
    border-color: #c6e2ff;
    background-color: #ecf5ff;
  }
}

之后 index.scss (整个样式的入口文件)需要引入button.scss

@use "components/button.scss";

最后在 examples/main.ts 中引入样式入口文件:

// 引入组件的样式代码
import "@fuxui-plus/theme-chalk/src/index.scss"

组件的编写

Button 组件实际上主要就是样式的书写,核心的原理是根据传入的 props,挂载对应的样式类,然后编写对应样式类的样式即可

<template>
  <button class="fux-button"
    :class="[
      `fux-button-${type}`,
      {
        'is-plain': plain,
        'is-round': round,
        'is-circle': circle,
        'is-disabled': disabled,
      }
    ]"
    :disabled="disabled"
  >
    <slot></slot>
  </button>
</template>

<script lang="ts">
import { defineComponent } from "vue";
import { buttonProps } from "./button";
export default defineComponent({
  name: "FuxButton",
  props: buttonProps,
});
</script>

<style scoped></style>

其中要注意一下 disabled,挂上 is-disabled 样式类之后,只是让这个按钮按上去像被禁用了,只是样式层面的实现,如果要禁用该按钮,还需要添加 disabled 属性。

icon

要使用的 icon,一般会选择从 iconfont 上面去下载。

将对应的字体文件放入到 fonts 目录下面,在 icon.scss 中放入字体图标样式:

// icon.scss
@use "./mixins/mixins.scss" as *;

@font-face {
  font-family: "element-icons";
  src:
    url("./fonts/element-icons.woff") format("woff"),
    /* chrome, firefox */ url("./fonts/element-icons.ttf") format("truetype"); /* chrome, firefox, opera, Safari, Android, iOS 4.2+*/
  font-weight: normal;
  font-display: inline;
  font-style: normal;
}

// <i class="fux-icon-ice-cream-round"></i>

// fux-icon
[class*="#{$namespace}-icon-"] {
  /* use !important to prevent issues with browser extensions that change fonts */
  font-family: "element-icons" !important;
  // speak: none;
  font-style: normal;
  font-weight: normal;
  font-variant: normal;
  text-transform: none;
  line-height: 1;
  vertical-align: baseline;
  display: inline-block;

  /* Better Font Rendering =========== */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}
...

在样式的入口文件引入 icon.scss

@use 'icon.scss'

之后在 button.vue 中添加如下的代码:

<i v-if="icon" :class="`fux-icon-${icon}`"></i>

事件

按钮的事件只有一个点击事件,父组件在使用我们的 button 组件的时候,会绑定一个 click 事件,因此我们要做的仅仅是触发父组件传递过来的事件回调即可。

对应的代码如下:

<button
    ...
    @click="clickHandle"
>
    ...
</button>
export default defineComponent({
  name: "FuxButton",
  props: buttonProps,
  emits: ["click"],
  setup(_, { emit }) {
    function clickHandle(e: Event) {
      emit("click", e);
    }
    return {
      clickHandle,
    };
  },
});