组件的编写
这个想必大家都已经非常熟悉了,这里我们只以一个 Button
组件为例,来介绍如何编写一个组件,然后再在后续「迭代增强」中不断添加新功能。
一、需求拆解
一般说来写组件的目标肯定是要满足一些指定的需求,所以我们要对需求进行一个拆解。
基础功能
- 支持不同状态(正常、悬浮(
hover
)、按下(focus
/active
)、禁用(disable
))。 - 可通过
props
控制样式变体(primary
/secondary
/danger
等)。 - 支持不同尺寸
size
(小sm
、中md
、大lg
)。 - 支持点击事件。
- 支持不同状态(正常、悬浮(
可插槽(slot)
- 默认 slot 渲染按钮文本或自定义内容。
扩展性
- 支持图标(
icon
)和加载态(loading
)。 - 支持加载中禁用。
- 增加动画(比如过渡、点击水波纹效果(
antd
就做了这个效果))。
- 支持图标(
主题化 & 样式隔离
- 使用
CSS
变量或预处理器便于全局定制。 scoped
样式避免冲突。
- 使用
(进阶)可访问性(a11y)
role="button"
、aria-pressed
、aria-disabled
等属性。- 键盘可聚焦与回车/空格触发。
二、初始版本:BaseButton.vue
vue
<script setup lang="ts">
import { computed } from 'vue'
export interface Props {
label?: string
variant?: 'primary' | 'secondary' | 'danger' | 'warning' | 'default'
size?: 'sm' | 'md' | 'lg'
disabled?: boolean
loading?: boolean
}
const props = withDefaults(defineProps<Props>(), {
disabled: false,
loading: false,
label: '',
size: 'md',
variant: 'default',
})
const emit = defineEmits<{
(e: 'click', event: MouseEvent): void
}>()
const sizes = {
sm: 'px-2 py-1 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg',
}
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
danger: 'bg-red-600 text-white hover:bg-red-700',
warning: 'bg-yellow-600 text-white hover:bg-yellow-700',
default: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
}
const buttonClasses = computed(() => [
'inline-flex items-center justify-center font-medium rounded transition duration-150',
sizes[props.size],
variants[props.variant],
(props.disabled || props.loading) && 'opacity-50 cursor-not-allowed',
])
function handleClick(e: MouseEvent) {
if (props.disabled || props.loading) {
return
}
emit('click', e)
}
</script>
<template>
<button
:class="buttonClasses" :disabled="disabled || loading" role="button" :aria-disabled="disabled || loading"
@click="handleClick"
>
<slot>{{ label }}</slot>
</button>
</template>
<style scoped>
button:focus {
outline: none;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.6);
}
</style>
说明
- 使用
<script setup>
+ TypeScript。 - 通过
computed
动态拼接类名,利用 Tailwind CSS 实现快速样式。 - 已考虑禁用与加载态。
三、迭代增强
接下来,我们分几轮不断扩展:
- 图标 & 加载指示
- 多主题 & 配色
- 波纹 / 动画效果
- 更多可定制接口
1. 添加 Icon 与 Loading
css
/* loading 动画 */
.spinner {
border: 2px solid rgba(255,255,255,0.3);
border-top-color: white;
border-radius: 50%;
width: 1em; height: 1em;
animation: spin 0.8s linear infinite;
display: inline-block;
}
@keyframes spin { to { transform: rotate(360deg); } }
2. 支持多主题与全局定制
- 在全局引入一组 CSS 变量(例如
--btn-bg-primary
、--btn-text-primary
)。 - 组件切换为引用变量,用户可在根节点或主题包里覆盖。
css
:root {
--btn-bg-primary: #3b82f6;
--btn-text-primary: #fff;
/* … */
}
vue
<!-- Button.vue 中 -->
:style="{
'--bg': `var(--btn-bg-${variant})`,
'--text': `var(--btn-text-${variant})`
}"
class="bg-[var(--bg)] text-[var(--text)]"
3. 动画与波纹效果
利用 CSS 或简单 JS 在点击时添加一个水波纹动画:
css
.btn-ripple {
position: relative;
overflow: hidden;
}
.btn-ripple .ripple {
position: absolute;
border-radius: 50%;
transform: scale(0);
animation: ripple 0.6s linear;
background: rgba(255,255,255,0.7);
}
@keyframes ripple {
to { transform: scale(4); opacity: 0; }
}
在点击时往按钮元素中插入 .ripple
元素并自动清理。
4. 更多可定制接口
as
属性:支持渲染为不同标签(<a>
、<router-link>
)。loadingText
:加载时显示不同文案。block
:一行占满宽度。rounded
:不同圆角等级。
下一步
在完成组件开发之后,我们就要进入下一步,组件的构建