封装Button组件
在封装组件之前,首先需要对组件进行一个简单的分析:
- 属性(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/info) | string | default |
| plain | 是否是朴素按钮 | boolean | false |
| round | 是否是圆角按钮 | boolean | false |
| circle | 是否是圆形按钮 | boolean | false |
| disabled | 是否禁用按钮 | boolean | false |
| 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
- src
首先我们在 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,
};
},
});