在 Vue 3 中二次封裝組件是提高代碼復用性和維護性的重要手段。以下是詳細的封裝方法和最佳實踐:
一、封裝原則
- 功能擴展:在原有組件基礎上添加新功能
- 定制樣式:統一項目的 UI 設計規范
- 簡化接口:隱藏復雜邏輯,提供簡潔的 API
- 復用邏輯:封裝可復用的業務邏輯
二、基礎封裝方法
1. 屬性透傳(Props)
<script setup>
import BaseButton from './BaseButton.vue'const props = defineProps({// 擴展原生屬性size: {type: String,default: 'medium'},// 新增自定義屬性loading: Boolean
})
</script><template><BaseButtonv-bind="$attrs" // 透傳所有未聲明的屬性:class="[`btn-size-${size}`,{ 'is-loading': loading }]"><slot /> <!-- 插槽透傳 --></BaseButton>
</template>
2. 事件透傳
<script setup>
const emit = defineEmits(['click', 'custom'])function handleClick(e) {// 執行擴展邏輯console.log('擴展的點擊處理')// 透傳原始事件emit('click', e)// 觸發自定義事件if (e.target.value) {emit('custom', e.target.value)}
}
</script><template><BaseInput @click="handleClick"@focus="$emit('focus', $event)" // 直接透傳/>
</template>
三、高級封裝技巧
1. 插槽透傳(包含作用域插槽)
<template><BaseTable><!-- 默認插槽 --><slot /><!-- 具名插槽透傳 --><template v-for="(_, slotName) in $slots" #[slotName]="scope"><slot :name="slotName" v-bind="scope || {}"/></template><!-- 擴展功能插槽 --><template #actions="scope"><div class="custom-actions"><slot name="custom-actions" v-bind="scope"/><button @click="handleExtraAction">+</button></div></template></BaseTable>
</template>
2. 方法暴露(使用 defineExpose)
<script setup>
import { ref } from 'vue'const innerComponent = ref(null)// 封裝內部組件的 focus 方法
function focus() {innerComponent.value?.focus()
}// 暴露給父組件的方法
defineExpose({focus,customMethod: () => console.log('自定義方法')
})
</script><template><BaseInput ref="innerComponent" />
</template>
3. 全局配置注入
<script setup>
import { inject } from 'vue'// 注入全局配置
const globalConfig = inject('formConfig', {labelWidth: '120px',validateOnChange: true
})const props = defineProps({// 允許組件級覆蓋全局配置labelWidth: String
})// 最終使用的配置
const actualLabelWidth = computed(() => props.labelWidth || globalConfig.labelWidth
)
</script>
四、最佳實踐
1. 支持 TypeScript 類型
<script setup lang="ts">
import type { BaseButtonProps } from './BaseButton.vue'interface ExtendedProps {// 新增屬性loading?: boolean// 繼承基礎組件的類型color?: 'primary' | 'success' | 'warning'
}// 合并原始屬性和擴展屬性
defineProps<BaseButtonProps & ExtendedProps>()
</script>
2. 默認值處理(合并策略)
<script setup>
import { computed } from 'vue'const props = defineProps({modelValue: { type: [String, Number], default: '' },config: {type: Object,default: () => ({maxLength: 100,showCounter: true})}
})const mergedConfig = computed(() => ({maxLength: 200, // 覆蓋默認值showCounter: props.config.showCounter, // 繼承配置trimOnBlur: true // 新增功能
}))
</script>
3. 樣式封裝(Scoped CSS)
<style scoped>
/* 深度選擇器修改子組件樣式 */
:deep(.base-input__inner) {border-radius: 8px;
}/* 封裝通用樣式類 */
.btn.primary {background: linear-gradient(to right, #ff8a00, #da1b60);
}
</style>
五、完整示例:封裝一個增強型 Input
<template><div class="enhanced-input"><label v-if="label">{{ label }}</label><BaseInput:modelValue="innerValue"v-bind="filteredAttrs":class="{ 'has-error': errorMessage }"@update:modelValue="handleChange"><template #prefix><slot name="prefix" /></template></BaseInput><div v-if="showCounter" class="counter">{{ valueLength }}/{{ maxLength }}</div><div v-if="errorMessage" class="error">{{ errorMessage }}</div></div>
</template><script setup>
import { computed, ref, watch, useAttrs } from 'vue'const props = defineProps({modelValue: [String, Number],label: String,maxLength: {type: Number,default: 100},rules: Array // 校驗規則
})const emit = defineEmits(['update:modelValue', 'change'])const attrs = useAttrs()
const innerValue = ref(props.modelValue)
const errorMessage = ref('')// 過濾不需要透傳到 BaseInput 的屬性
const filteredAttrs = computed(() => {const { label, rules, ...rest } = attrsreturn rest
})// 值處理
const valueLength = computed(() => innerValue.value?.toString().length || 0)
const showCounter = computed(() => props.maxLength > 0)// 值變化處理
function handleChange(value) {innerValue.value = valueemit('update:modelValue', value)emit('change', value)validate(value)
}// 校驗邏輯
function validate(value) {if (!props.rules) returnconst rule = props.rules.find(rule => {if (rule.required && !value) return trueif (rule.pattern && !rule.pattern.test(value)) return true// 可擴展更多規則...})errorMessage.value = rule ? rule.message : ''
}// 暴露驗證方法
defineExpose({ validate })
</script>
六、封裝建議
- 保持接口簡單:避免暴露過多內部細節
- 遵循約定大于配置:提供合理的默認值
- 文檔注釋:使用 JSDoc 說明組件用法
- 可組合性:將復雜組件拆分為多個小組件
- 錯誤處理:添加邊界情況處理
- 類型安全:為 TypeScript 項目提供類型定義
通過合理封裝,可以顯著提高開發效率和代碼質量。建議根據實際項目需求選擇合適級別的封裝,避免過度封裝導致的維護成本增加。