【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 ⭐,您的支持就是它们成长的最大动力,在此谢过各位大侠啦!

相关推荐
Jedi Hongbin8 分钟前
echarts自定义图表--仪表盘
前端·javascript·echarts
凯哥197012 分钟前
Sciter.js指南 - 桌面GUI开发时使用第三方模块
前端
边洛洛12 分钟前
对Electron打包的exe文件进行反解析
前端·javascript·electron
财神爷亲闺女13 分钟前
js 实现pc端鼠标横向拖动滚动
前端
用户20311966009613 分钟前
sheet在SwiftUI中的基本用法
前端
晴殇i14 分钟前
一行代码搞定防抖节流:JavaScript新特性解析
前端·javascript
David凉宸17 分钟前
HTML表单(二)
前端
G扇子17 分钟前
深入解析CSRF攻击:从攻击机制到多层次防护策略
前端·安全