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 公共组件库。