序言
在之前的探索中,我们成功雕琢出了功能完备且样式精美的 Vue 按钮组件,它在前端界面上绽放着独特的光彩✨。然而,前端开发的创新之路永无止境。如今,为了满足更丰富的交互需求,我们将目光聚焦在按钮组组件的实现上。在已有按钮组件的坚实基础上,我们将如何构建出功能强大的按钮组组件呢🧐?让我们一同开启这场充满挑战与惊喜的开发之旅。
UI
我们继续参考 Element 的优秀设计,来打造按钮组的 UI。

这张图中可以看出,按钮组组件的实现相对简单,它是基于我们已经实现的按钮组件进行一些细微样式改造而成的。这就像是在精美的画作上进行二次创作🖌️,让它焕发出新的魅力。
准备工作
按照开发惯例,我们首先要在 packages/components/button-group/src
目录下,新增 button.ts
和 button.vue
文件📄,为后续的开发奠定基础。然后在 packages/components/button-group
目录下新增 index.ts
作为组件的入口文件,它负责将各个部分整合在一起。
props属性
在button-group.ts
文件中,我们需要定义所需的 props
。为了让使用更加便捷,这里用到的 props
与按钮组件的 props
保持一致。这样,我们就能直接设置每个按钮组件所需要的属性,避免了每个按钮单独设置属性带来的繁琐和不友好。
ts
import { ExtractPropTypes } from 'vue'
import { buttonProps } from '../../button/src/button'
export const buttonGroupProps = {
...buttonProps,
} as const
export type ButtonGroupType = ExtractPropTypes<typeof buttonGroupProps>
由于是沿用按钮组件的 props
,所以直接引入使用,代码简洁明了。
简洁的模板部分
模板部分的实现更加简单,我们只需要提供一个容器,并使用插槽功能即可。
html
<template>
<div :class="ns.b()">
<slot></slot>
</div>
</template>
脚本部分: 统一设置按钮属性的奥秘
ts
<script lang="ts" setup>
import { provide, reactive, toRef, toRefs } from 'vue'
import { useNamespace } from '@nova-ui/hooks'
import { buttonGroupProps } from './button-group'
import { buttonGroupInjectionKey } from './constants'
const ns = useNamespace('button-group')
defineOptions({
name: 'NButtonGroup',
})
const props = defineProps(buttonGroupProps)
provide(
buttonGroupInjectionKey,
props
)
</script>
开头导入了 Vue 相关工具函数,其中provide
是数据传递的关键 。useNamespace
用于创建按钮组专属命名空间,避免命名冲突。从button-group
文件引入buttonGroupProps
,它包含了按钮组的各种属性定义,buttonGroupInjectionKey
则是依赖注入的关键标识。
代码主体部分,通过useNamespace
创建命名空间ns
,用defineOptions
定义组件名为NButtonGroup
。然后defineProps
依据buttonGroupProps
定义组件属性,最后利用provide
,以buttonGroupInjectionKey
为桥梁,将属性props
传递给后代组件,从而实现所有按钮属性的统一设置,确保按钮组整体的一致性 。
改造按钮组件
在开发按钮组组件时,仅仅在按钮组组件中设置 provide
来传递属性是不够完善的。按钮组件自身也需要进行相应改造,以接收按钮组组件传递的属性,并将其与自身属性合并后应用到组件中。在实际开发中,这种合并父组件属性的需求较为常见,比如在表单组件统一设置属性时也会遇到。因此,为了提升开发效率,方便后续组件开发,我们创建了一个公共方法。下面这段代码就是在 packages/hooks/use - prop - inject
目录下新增的 index.ts
文件内容。
在 packages/hooks/use -prop-inject
目录下 新增 index.ts
文件:
ts
import { isBoolean } from "@nova-ui/utils";
import { computed, ComputedRef, inject, InjectionKey } from "vue";
export const usePropInject = <T, U extends object>(key: InjectionKey<T>, props: U): ComputedRef<U> => {
return computed(() => {
const context = inject<T | undefined>(key, undefined)
if (!context) {
return props;
}
const _props: U = {} as U
for (const key in context) {
if (Object.prototype.hasOwnProperty.call(context, key)) {
const element = context[key];
const _key = key as unknown as keyof U
const value = props[_key];
if (isBoolean(value)) {
_props[_key] = (value ? value : element) as U[keyof U];
} else {
_props[_key] = (value !== undefined ? value : element) as U[keyof U];
}
}
}
return _props
})
}
代码开头,从@nova-ui/utils
引入isBoolean
函数判断值是否为布尔类型。从 Vue 框架引入computed
创建计算属性、ComputedRef
作类型定义、inject
接收传递数据、InjectionKey
标识注入数据。
usePropInject
函数接收key
(查找注入数据用)和props
(按钮组件自身属性),返回计算属性。函数内,用computed
创建计算属性函数,尝试获取传递数据context
。若没获取到,返回props
;获取到则进行属性合并。
创建空的_props
存放合并属性,遍历context
属性,确保是自身属性后,获取属性值element
,对应按钮组件自身属性值value
。布尔类型的value
按条件决定合并值,非布尔类型的value
若不为undefined
则用自身值,否则用element
。最后返回合并后的_props
,实现属性合并功能。
这段代码通过合理设计,为按钮组件及类似组件合并父组件属性提供了实用的公共方法,助力组件间属性传递与整合。
最后在按钮组件中增加const _props = usePropInject<ButtonGroupType, ButtonType>(buttonGroupInjectionKey, props)
这段代码,并将组件运用的属性前面都加上_props
即可完成改造。
按钮组组件样式
按钮组组件的样式相对简单,只做了一些细微调整。
scss
@use 'sass:map';
@use 'mixins' as *;
$button: #{$namespace}-button;
@include b(button-group) {
display: inline;
vertical-align: middle;
line-height: 1;
& + & {
margin-left: getVariableValue('space');
}
& > .#{$button} {
position: relative;
& + .#{$button} {
margin-left: -1px;
}
&:first-child {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
&:last-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
&:not(:first-child):not(:last-child) {
border-radius: 0;
z-index: 1;
}
&:hover {
z-index: 2;
}
& + .is-link {
margin-left: getVariableValue('space');
}
}
@each $scene in $sceneTypes {
.#{$button}--#{$scene} {
&:first-child {
border-right-color: getVariableValue('border-color');
}
&:last-child {
border-left-color: getVariableValue('border-color');
}
&:not(:first-child):not(:last-child) {
border-left-color: getVariableValue('border-color');
border-right-color: getVariableValue('border-color');
}
}
}
}
最终使用效果
html
<div v-for="type in buttonTypes" :key="type" style="margin-bottom: 20px">
<NButtonGroup :type="type">
<NButton v-for="item in 3" :key="item">default-{{ item }}</NButton>
</NButtonGroup>
<br>
<NButtonGroup v-for="scene in scenes" :key="scene" :scene="scene" :type="type">
<NButton v-for="item in 3" :key="item">{{ scene }}-{{ item }}</NButton>
</NButtonGroup>
</div>
通过这样的设置,我们可以轻松地使用按钮组组件,满足各种不同的业务需求。
🦀🦀感谢看官看到这里,如果觉得文章不错的话🙌,点个关注不迷路⭐。
诚邀您加入我的微信技术交流群🎉,群里都是志同道合的开发者👨💻,大家能一起交流分享摸鱼🐟。期待与您在群里相见🚀,咱们携手在开发路上共同进步✨ !
👉点我
感谢各位大侠一路相伴,实在感激! 不瞒您说,在下还有几个开源项目 📦,它们就像精心培育的幼苗 🌱,急需您的浇灌。要是您瞧着还不错,麻烦动动手指,给它们点亮几颗 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...