vue<!-- components/BaseButton.vue --><template>
<button
:type="type"
:class="['base-btn', sizeClass]"
@click="$emit('click', $event)"
>
<slot>默认按钮</slot>
</button>
</template>
<script setup>
defineProps({
type: {
type: String,
default: 'button',
validator: (v) => ['button', 'submit', 'reset'].includes(v)
},
size: {
type: String,
default: 'medium',
validator: (v) => ['small', 'medium', 'large'].includes(v)
}
})
defineEmits(['click'])
const sizeClass = computed(() => `btn-${props.size}`)
</script>
<style scoped>
.base-btn {
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
}
.btn-small { font-size: 12px; }
.btn-medium { font-size: 14px; }
.btn-large { font-size: 16px; }
</style>
vue<!-- components/BaseModal.vue --><template>
<div v-show="visible" class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container">
<div class="modal-header">
<slot name="header">
<h3>{{ title }}</h3>
</slot>
<button @click="close">×</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="footer">
<BaseButton @click="close">关闭</BaseButton>
</slot>
</div>
</div>
</div>
</div>
</template>
<script setup>
defineProps({
visible: Boolean,
title: String
})
const emit = defineEmits(['update:visible'])
const close = () => {
emit('update:visible', false)
}
</script>
vue<template> <BaseModal v-model:visible="showModal" title="提示"> <p>这是模态框内容</p> <template #footer> <BaseButton @click="confirm">确认</BaseButton> </template> </BaseModal> </template>
vue<!-- components/MySelect.vue --><template>
<div class="select-wrapper">
<div class="selected" @click="toggle">
{{ selectedLabel }}
</div>
<div v-show="isOpen" class="options">
<slot></slot>
</div>
</div>
</template>
<script setup>
import { provide, ref } from 'vue'
const isOpen = ref(false)
const selectedLabel = ref('请选择')
const selectedValue = ref(null)
const toggle = () => (isOpen.value = !isOpen.value)
const selectOption = (value, label) => {
selectedValue.value = value
selectedLabel.value = label
isOpen.value = false
}
// 向子组件提供上下文
provide('selectContext', { selectOption })
</script>
vue<!-- components/MyOption.vue --><template>
<div class="option" @click="handleClick">
<slot></slot>
</div>
</template>
<script setup>
import { inject } from 'vue'
const props = defineProps({
value: [String, Number],
label: String
})
const { selectOption } = inject('selectContext')
const handleClick = () => {
selectOption(props.value, props.label || props.value)
}
</script>
单一职责原则:每个组件只专注一个功能
可配置性:通过 Props 控制组件行为
可扩展性:使用 Slots 允许内容定制
无障碍访问:添加 ARIA 属性
vue<!-- 懒加载组件 --><script setup>
import { defineAsyncComponent } from 'vue'
const HeavyComponent = defineAsyncComponent(() =>
import('./HeavyComponent.vue')
)
</script>
typescript// types/components.d.tsdeclare module '*.vue' { import type { DefineComponent } from 'vue' const component: DefineComponent<{}, {}, any> export default component}// 按钮组件 Propsexport interface ButtonProps { type?: 'primary' | 'success' | 'warning' size?: 'small' | 'medium' | 'large'}
markdown## BaseButton 按钮### Props| 属性 | 类型 | 默认值 | 说明 ||------|------|--------|-----|| type | String | 'primary' | 按钮类型 || size | String | 'medium' | 按钮尺寸 |### Slots| 名称 | 说明 ||------|------|| default | 按钮内容 |### 示例```vue<BaseButton type="success" @click="handleClick"> 提交</BaseButton>
typescript// main.tsimport { createApp } from 'vue'import App from './App.vue'// 自动全局注册const app = createApp(App)const modules = import.meta.glob('./components/base/*.vue')Object.entries(modules).forEach(([path, module]) => { const name = path.split('/').pop()?.replace('.vue', '') app.component(name!, defineAsyncComponent(module))})app.mount('#app')
typescript// tests/BaseButton.spec.tsimport { mount } from '@vue/test-utils'import BaseButton from '../components/BaseButton.vue'test('emits click event', async () => { const wrapper = mount(BaseButton) await wrapper.trigger('click') expect(wrapper.emitted().click).toBeTruthy()})test('displays slot content', () => { const wrapper = mount(BaseButton, { slots: { default: '测试按钮' } }) expect(wrapper.text()).toContain('测试按钮')})
| 特性 | Vue 2 | Vue 3 |
|---|---|---|
| 组件定义 | Options API | Composition API + <script setup> |
| Props 类型校验 | prop: { type: Object } | TypeScript 接口 + PropType |
| 事件通信 | this.$emit | defineEmits |
| 插槽语法 | slot 和 slot-scope | v-slot 和 # 简写 |
| 全局组件注册 | Vue.component | app.component |
保持组件原子化:每个组件只解决一个具体问题
合理使用 Composition API:复杂逻辑使用 hooks 抽离
类型安全:结合 TypeScript 强化组件接口
文档驱动:为每个组件编写使用文档
自动化测试:保证组件质量的关键手段
通过以上模式,您可以创建出高复用性、易维护的 Vue 3 公共组件库。