机试:封装通用Button组件

674 阅读5分钟

引言

在上一篇文章中,我们介绍了如何使用 Vue 3 和 TypeScript 封装一个全局通用的表格组件,虽然比较简单基础,但是能够帮助大家更深入理解 Vue 3 通用组件的设计思路。今天,我们将继续深入,介绍如何封装一个通用的按钮组件,使其可以像 Element Plus 一样方便调用。通用按钮组件在项目开发中是高频使用的基础组件,它不仅要满足不同的样式需求,还需具备灵活的事件处理能力。

image.png

正文

1. 通用按钮组件的需求分析

按钮组件的需求主要包括:

  • 多样化的样式:常见的按钮类型包括 primarysecondarydanger 等。
  • 多种大小:支持 smallmediumlarge 三种大小,以适应不同的页面布局。
  • 禁用状态:按钮需具备禁用功能,以便在特定情况下禁用用户的点击操作。
  • 事件传递:按钮的点击事件需能正确传递到父组件,以实现不同的交互。

2. 组件设计与封装的步骤

2.1 使用 Props 设计按钮样式、大小和禁用状态

CustomButton.vue 中定义按钮的 typesizedisabled 属性,使用 props 让父组件灵活传递这些参数。我们将使用 TypeScript 定义属性类型,确保代码的可靠性。

<!-- CustomButton.vue -->
<script setup lang="ts">
import { withDefaults, defineProps } from 'vue'

// 定义组件名称 
defineOptions({ 
    name: 'ElButton' 
});

interface ButtonProps {
    type?: 'primary' | 'secondary' | 'danger'; // 按钮类型
    size?: 'small' | 'medium' | 'large';       // 按钮大小
    disabled?: boolean;                        // 禁用状态
}

// 使用 withDefaults 为 props 设置默认值
const props = withDefaults(defineProps<ButtonProps>(), {
    type: 'primary',      // 默认按钮类型为 primary
    size: 'medium',       // 默认大小为 medium
    disabled: false       // 默认不禁用
})
</script>

<style scoped>
.custom-button { /* 样式定义省略... */ }
</style>

defineOptions:重新为组件定义组件名。

withDefaults:为 props 设置默认值,避免父组件未传递时出现问题。

类型约束:通过定义 ButtonProps 接口,明确每个 prop 的类型及可选值,提升代码的规范性。

2.2 实现按钮点击事件传递与控制

按钮的核心功能是响应用户点击,因此需要处理点击事件,并将其传递到父组件。

<script setup lang="ts">
import { defineEmits } from 'vue'

const emit = defineEmits<{
    (e: 'click', event: MouseEvent): void
}>()

const handleClick = (event: MouseEvent) => {
    if (!props.disabled) { // 禁用状态下不触发点击事件
        emit('click', event) // 通过 emit 将事件传递给父组件
    }
}
</script>

如果你没有提供类型注解,emit('click', event) 可以传递任何类型的参数,这可能会导致事件处理函数接收到的参数不符合预期。通过给 emit 定义类型,可以避免这种错误。:void 表示该事件触发后没有返回值。

2.3 编写模板和样式

接下来便是给不同按钮编写它们的模板以及样式

<template>
    <button 
        :class="['custom-button', props.type, props.size]" 
        :disabled="props.disabled" 
        @click="handleClick">
        <slot></slot> <!-- 插槽,用于插入按钮内容 -->
    </button>
</template>

<style scoped>
.custom-button {
    border: none;
    border-radius: 5px;
    font-size: 16px;
    cursor: pointer;
    transition: background-color 0.3s, padding 0.3s, font-size 0.3s;
}

/* 按钮类型样式 & 悬停样式 */
.custom-button.primary {
    background-color: #007bff;
    color: white;
}

.custom-button.primary:hover {
    background-color: #0056b3; /* 悬停时变为深蓝色 */
}

.custom-button.secondary {
    background-color: #6c757d;
    color: white;
}

.custom-button.secondary:hover {
    background-color: #5a6268; /* 悬停时变为深灰色 */
}

.custom-button.danger {
    background-color: #dc3545;
    color: white;
}

.custom-button.danger:hover {
    background-color: #c82333; /* 悬停时变为深红色 */
}

/* 按钮大小样式 */
.custom-button.small {
    padding: 5px 10px;
    font-size: 12px;
}

.custom-button.medium {
    padding: 10px 20px;
    font-size: 16px;
}

.custom-button.large {
    padding: 15px 30px;
    font-size: 20px;
}

/* 禁用状态样式 */
.custom-button:disabled {
    background-color: #e0e0e0;
    cursor: not-allowed;
}
</style>
  1. 模板部分

    • :class 动态绑定类名,根据 props.typeprops.size 设置不同的样式。
    • :disabled 控制按钮的禁用状态。
    • <slot> 提供插槽,用于插入按钮文本或图标内容。
  2. 样式部分

    • 基础样式如 borderborder-radius 定义按钮的通用外观。
    • 使用 BEM 风格(如 .custom-button.primary)对不同类型按钮的样式进行分类。
    • 通过 hover 状态增强交互效果。

3. 全局注册组件

和之前我们定义通用 table 组件一样,通用组件通常需要在整个项目中复用,因此需要全局注册。

  1. index.ts 中封装注册逻辑:
import { App } from 'vue'
import ElButton from './CustomButton.vue'

export default {
    install(app: App) {
        app.component('ElButton', ElButton)
    }
}
  • install 方法是 Vue 插件的标准接口,用于在 app 实例上注册全局组件。

  • app.component 方法将 ElButton 注册为全局组件,命名为 'ElButton'

  1. main.ts 中引入组件插件并注册:
import { createApp } from 'vue'
import App from './App.vue'
import ElButton from './components/CustomButton.vue'

const app = createApp(App)
app.use(ElButton) // 全局注册组件
app.mount('#app')

4. 父组件调用示例

在父组件中,我们可以按需配置按钮类型、大小和禁用状态,同时监听点击事件。

<template>
    <div class="parent-container">
        <h1>父组件</h1>
        <!-- 使用不同大小的自定义按钮 -->
        <!-- 使用 :用于绑定动态值(如变量、表达式)。不使用 :直接传递静态字符串。 -->

        <el-button 
            type="primary" 
            size="small" 
            :disabled="isDisabled" 
            @click="handleButtonClick"
        >
            Small Primary
        </el-button>

        <el-buttonel 
            type="secondary" 
            size="medium" 
            @click="handleButtonClick"
        >
            Medium Secondary
        </el-buttonel>

        <el-button 
            type="danger" 
            size="large" 
            @click="handleButtonClick"
        >
            Large Danger
        </el-button>

        <!-- 切换禁用状态 -->
        <label>
            <input type="checkbox" v-model="isDisabled" /> Disable Button
        </label>

        <button @click="toggleDisable">
            {{ isDisabled ? '启用按钮' : '禁用按钮' }}
        </button>
    </div>
</template>

<script setup lang="ts">
import CustomButton from './components/CustomButton.vue'
import { ref } from 'vue';

const isDisabled = ref(false);

const handleButtonClick = (event: MouseEvent) => {
    alert('按钮被点击了!');
};

const toggleDisable = () => {
    isDisabled.value = !isDisabled.value;
};
</script>

<style scoped>
.parent-container {
    display: flex;
    flex-direction: column;
    align-items: center;
}

button {
    margin-top: 20px;
    padding: 10px 15px;
    font-size: 16px;
}
</style>
  • 通过 typesize 属性配置按钮样式,使用 v-model 动态绑定按钮的禁用状态。

效果展示

button.gif

总结与反思

Button 组件通过支持多种类型、尺寸和状态控制,实现了通用性和高定制化,适应了不同的交互场景。借助 emit 实现事件传递,并封装为全局组件,提升了使用便捷性,是简化按钮功能开发的良好实践。希望能够在面试中帮助到你。

记得点赞收藏+关注哦

点赞.jfif