【Nova UI】十四、打造组件库之按钮组件(下):按钮组组件的构建之旅

序言

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

UI

我们继续参考 Element 的优秀设计,来打造按钮组的 UI。

这张图中可以看出,按钮组组件的实现相对简单,它是基于我们已经实现的按钮组件进行一些细微样式改造而成的。这就像是在精美的画作上进行二次创作🖌️,让它焕发出新的魅力。

准备工作

按照开发惯例,我们首先要在 packages/components/button-group/src 目录下,新增 button.tsbutton.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 ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!

相关推荐
Trust yourself24311 分钟前
想把一个easyui的表格<th>改成下拉怎么做
前端·深度学习·easyui
苹果醋312 分钟前
iview中实现点击表格单元格完成编辑和查看(span和input切换)
运维·vue.js·spring boot·nginx·课程设计
武昌库里写JAVA15 分钟前
iView Table组件二次封装
vue.js·spring boot·毕业设计·layui·课程设计
三口吃掉你16 分钟前
Web服务器(Tomcat、项目部署)
服务器·前端·tomcat
Trust yourself24318 分钟前
在easyui中如何设置自带的弹窗,有输入框
前端·javascript·easyui
烛阴22 分钟前
Tile Pattern
前端·webgl
前端工作日常1 小时前
前端基建的幸存者偏差
前端·vue.js·前端框架
Electrolux1 小时前
你敢信,不会点算法没准你赛尔号都玩不明白
前端·后端·算法
a cool fish(无名)2 小时前
rust-参考与借用
java·前端·rust
Feather_742 小时前
从Taro的Dialog.open出发,学习远程控制组件之【事件驱动】
javascript·学习·taro