序言
在上一篇文章中,我们深入探索了 icon 组件从测试到全局注册的全过程🎯,成功为其在项目中稳定运行筑牢了根基。此刻,组件库的建设之旅仍在继续,我们将目光聚焦于另一个关键组件 ------ 按钮组件。按钮作为用户与界面交互的核心纽带🧐,其功能的完备性、样式的美观性以及操作的流畅性,都对用户体验起着至关重要的作用。接下来,让我们一同深入剖析按钮组件的实现过程,为组件库增添强大助力,使其在前端开发的舞台上绽放更加耀眼的光芒✨。
UI
我们先借鉴 Element Plus 组件库的样式,着手实现一个基础的按钮组件。参考图如下:

从图中可以分析出,该按钮具备default
、primary
、success
、info
、warning
、danger
这几种场景,它们的区别主要体现在边框颜色、字体颜色以及背景颜色上。结合我们的实际业务需求,我们开始打造自己的按钮组件
准备工作
按照惯例,我们首先在packages/components/button/src
目录下,新增button.ts
和button.vue
文件📄。
props属性
在button.ts
文件中,定义我们所需的props
。这些属性包括场景、大小、是否禁用、是否处于加载中、加载时的图标、按钮的前缀图标、后缀图标、按钮形状以及按钮类型。
ts
import { ExtractPropTypes, PropType } from 'vue'
import { useSceneProp, useSizeProp, useLoadingIconProp, useIconProp } from '@nova-ui/hooks'
export const buttonShapes = ['round', 'circle'] as const
export type ButtonShapeType = typeof buttonShapes[number]
export const buttonTypes = ['plain', 'text', 'link', 'dashed'] as const
export type ButtonTypeType = typeof buttonTypes[number]
export const buttonProps = {
scene: useSceneProp(),
size: useSizeProp(),
disabled: {
type: Boolean,
},
loading: {
type: Boolean,
},
loadingIcon: useLoadingIconProp(),
prefixIcon: useIconProp(),
suffixIcon: useIconProp(),
shape: {
type: String as PropType<ButtonShapeType>
},
type: {
type: String as PropType<ButtonTypeType>
}
} as const
export type ButtonType = ExtractPropTypes<typeof buttonProps>
相关代码如上述所示。其中,scene
场景属性很可能在多个组件中复用,因此我们将其单独提取出来(这是一个良好的编程习惯,当相同代码在多处使用时,建议提取以提高代码的可维护性)。在packages/constants
目录下新增scene.ts
文件,用于存储场景的常量及其类型。同时,在packages/hooks/use-scene
目录下新增index.ts
文件,编写我们需要使用的props
属性值。
ts
// packages/constants/scene.ts
export const scenes = ['primary', 'success', 'warning', 'danger', 'error', 'info'] as const
export type Scene = typeof scenes[number]
// packages/hooks/use-scene/index.ts
import { PropType } from 'vue'
import { Scene } from '@nova-ui/constants'
export const useSceneProp = () => ({
type: String as PropType<Scene>,
})
其它如size
、loadingIcon
、prefixIcon
、suffixIcon
等属性的提取方式与之类似,在此不再一一赘述。
模板部分:构建按钮的外观与交互结构
首先来看看这段代码的 HTML 模板部分,它决定了按钮在页面上的最终呈现效果,是按钮组件的 "外观设计师"👨🎨。
html
<template>
<button
:class="[
ns.b(),
ns.m(scene),
ns.m(size),
ns.is('disabled', disabled),
ns.is('loading', loading),
ns.is(!shape, !!shape),
ns.is(!type, !!type),
]"
>
<template v-if="loading">
<slot v-if="$slots.loading" name="loading"></slot>
<NIcon v-else :class="ns.e('loading')" :name="loadingIcon"></NIcon>
</template>
<template v-if="$slots.prefix || prefixIcon">
<slot v-if="$slots.prefix" name="prefix"></slot>
<NIcon v-else-if="prefixIcon" :class="ns.e('prefix')" :name="prefixIcon"></NIcon>
</template>
<slot></slot>
<template v-if="$slots.suffix || suffixIcon">
<slot v-if="$slots.suffix" name="suffix"></slot>
<NIcon v-else-if="suffixIcon" :class="ns.e('suffix')" :name="suffixIcon"></NIcon>
</template>
</button>
</template>
这段模板代码定义了按钮组件的可视化结构与交互元素。最外层的<button>
标签构建了按钮的基本框架,通过:class
绑定一系列动态类名,实现按钮外观的多样化。
ns.b()
提供了按钮的基础类名,是按钮样式的基石。ns.m(scene)
与ns.m(size)
则根据传入的scene
(场景)和size
(尺寸)参数,为按钮添加相应的修饰类名,使按钮能够适配不同的使用场景与布局需求。
ns.is
系列函数依据传入的布尔值属性,动态添加对应的状态类名。例如,ns.is('disabled', disabled)
在disabled
为true
时,为按钮添加表示禁用状态的类名,改变按钮的外观以提示用户该按钮当前不可操作。同理,ns.is('loading', loading)
根据loading
状态添加或移除加载相关的类名,实现加载状态下按钮的视觉反馈。
在按钮内容方面,代码充分利用 Vue 的插槽机制与NIcon
组件,实现了高度的灵活性。当按钮处于loading
状态时,首先检查是否存在名为loading
的插槽。若有,则渲染该插槽内容,允许开发者自定义加载状态下的显示元素;若没有,则渲染NIcon
组件作为加载图标,图标类名由ns.e('loading')
生成,图标名称则由loadingIcon
属性指定。
对于按钮的前缀和后缀部分,同样采用了灵活的判断逻辑。先判断是否存在prefix
插槽,若有则渲染插槽内容;若没有但设置了prefixIcon
属性,则渲染NIcon
组件作为前缀图标,类名由ns.e('prefix')
生成,图标名称由prefixIcon
指定。后缀部分的逻辑与之相同,通过这种方式,按钮可以轻松添加各种图标或自定义内容,极大地增强了按钮的功能性与美观性。
脚本部分:赋予组件功能与数据交互能力
脚本部分是按钮组件的核心,负责引入必要的模块、定义组件的配置与属性,如同为组件注入灵魂🧠。
ts
<script lang="ts" setup>
import { useNamespace } from '@nova-ui/hooks'
import { buttonProps, ButtonType } from './button'
import { NIcon } from '@nova-ui/components'
const ns = useNamespace('button')
defineOptions({
name: 'NButton',
})
defineProps(buttonProps)
</script>
useNamespace
函数用于生成具有统一规范的类名,确保按钮组件的样式管理清晰且易于维护。buttonProps
和ButtonType
从./button
文件引入,其中buttonProps
定义了按钮组件可接收的外部属性,如disabled
、loading
、size
等,为组件与外部的数据交互提供了接口。
NIcon
组件从@nova-ui/components
引入,用于在按钮中显示各种图标。通过const ns = useNamespace('button')
创建了按钮组件专属的样式命名空间实例,供模板部分生成类名使用。
defineOptions({name: 'NButton'})
为按钮组件定义了名称NButton
,方便在 Vue 项目中进行识别与引用。defineProps(buttonProps)
则依据buttonProps
定义了组件可接收的属性,将外部传入的数据与组件内部逻辑连接起来,使组件能够根据不同的属性值呈现出相应的状态与外观。
综上所述,这段代码通过模板与脚本的协同工作,实现了一个功能丰富、可定制性强的 Vue 按钮组件。它不仅能够满足各种常见的按钮使用场景,还为开发者提供了灵活的扩展空间,在前端组件库中具有重要的应用价值 。
🦀🦀感谢看官看到这里,如果觉得文章不错的话🙌,点个关注不迷路⭐。
诚邀您加入我的微信技术交流群🎉,群里都是志同道合的开发者👨💻,大家能一起交流分享摸鱼🐟。期待与您在群里相见🚀,咱们携手在开发路上共同进步✨ !
👉点我
感谢各位大侠一路相伴,实在感激! 不瞒您说,在下还有几个开源项目 📦,它们就像精心培育的幼苗 🌱,急需您的浇灌。要是您瞧着还不错,麻烦动动手指,给它们点亮几颗 Star ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!
Nova UI
组件库:github.com/gmingchen/n...- 基于 Vue3 + Element-plus 管理后台基础功能框架
- 预览:admin.gumingchen.icu
- Github:github.com/gmingchen/a...
- Gitee:gitee.com/shychen/agi...
- 基础版后端:github.com/gmingchen/j...
- 文档:admin.gumingchen.icu/doc/
- 基于 Vue3 + Element-plus + websocket 即时聊天系统
- 基于 node 开发的后端服务:github.com/gmingchen/n...