Vue3组合式API为前端工程化中的组件库设计提供了全新的架构思路。通过setup语法糖和TypeScript类型系统,可以构建类型安全、逻辑复用性强、API设计规范的企业级组件库。本文以表单组件库为例,完整演示Vue3组件设计模式、Props/Emits类型定义、组合式函数封装和样式方案。
组件库项目初始化
使用Vite搭建组件库开发环境,配置TypeScript严格模式和库模式打包:
# 创建项目
npm create vite@latest vue3-ui-lib -- --template vue-ts
cd vue3-ui-lib
npm install
# 安装开发依赖
npm install -D @vitejs/plugin-vue vue-tsc sass
npm install vue@3
vite.config.ts配置库模式打包:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { resolve } from 'path'
export default defineConfig({
plugins: [vue()],
resolve: {
alias: { '@': resolve(__dirname, 'src') }
},
build: {
lib: {
entry: resolve(__dirname, 'src/index.ts'),
name: 'VueUI',
fileName: (format) => `vue3-ui-lib.${format}.js`
},
rollupOptions: {
external: ['vue'],
output: {
globals: { vue: 'Vue' },
exports: 'named'
}
}
}
})
组件Props类型设计与校验
以Input组件为例,演示TypeScript类型定义与Vue3 props的整合方式:
// src/components/Input/types.ts
import type { ExtractPropTypes, PropType } from 'vue'
export type InputSize = 'small' | 'medium' | 'large'
export type InputType = 'text' | 'password' | 'number' | 'email'
export const inputProps = {
modelValue: {
type: [String, Number] as PropType,
default: ''
},
type: {
type: String as PropType,
default: 'text',
validator: (val: string) => ['text', 'password', 'number', 'email'].includes(val)
},
size: {
type: String as PropType,
default: 'medium'
},
placeholder: { type: String, default: '' },
disabled: { type: Boolean, default: false },
readonly: { type: Boolean, default: false },
clearable: { type: Boolean, default: false },
maxlength: { type: Number, default: -1 },
showCount: { type: Boolean, default: false }
} as const
export type InputProps = ExtractPropTypes
使用as const确保类型推导为字面量类型而非宽泛的string。ExtractPropTypes从props定义中自动生成完整类型,在IDE中获得精确的属性提示。
Emits事件类型定义
// src/components/Input/types.ts (续)
export const inputEmits = {
'update:modelValue': (value: string | number) => true,
'input': (value: string) => true,
'change': (value: string) => true,
'focus': (e: FocusEvent) => true,
'blur': (e: FocusEvent) => true,
'clear': () => true
}
export type InputEmits = typeof inputEmits
组合式函数封装可复用逻辑
将Input组件中可复用的状态管理和事件处理逻辑抽离为useInput组合式函数:
// src/components/Input/useInput.ts
import { ref, computed, watch } from 'vue'
import type { InputProps, InputEmits } from './types'
export function useInput(props: InputProps, emit: (e: string, ...args: any[]) => void) {
const isFocused = ref(false)
const isHovering = ref(false)
const inputClasses = computed(() => ({
'vui-input': true,
[`vui-input--${props.size}`]: true,
'vui-input--disabled': props.disabled,
'vui-input--focused': isFocused.value
}))
const displayValue = computed({
get: () => props.modelValue,
set: (val) => {
emit('update:modelValue', val)
emit('input', String(val))
}
})
const textLength = computed(() => {
const val = props.modelValue
return typeof val === 'string' ? val.length : String(val).length
})
const showClearIcon = computed(() =>
props.clearable && !props.disabled && !props.readonly && displayValue.value !== '' && isHovering.value
)
const handleFocus = (e: FocusEvent) => {
if (props.disabled || props.readonly) return
isFocused.value = true
emit('focus', e)
}
const handleBlur = (e: FocusEvent) => {
isFocused.value = false
emit('blur', e)
emit('change', String(displayValue.value))
}
const handleClear = () => {
displayValue.value = ''
emit('clear')
}
return {
isFocused,
isHovering,
inputClasses,
displayValue,
textLength,
showClearIcon,
handleFocus,
handleBlur,
handleClear
}
}
组件模板实现
<!-- src/components/Input/Input.vue -->
<template>
<div
:class="inputClasses"
@mouseenter="isHovering = true"
@mouseleave="isHovering = false"
>
<input
:type="type"
:value="displayValue"
:placeholder="placeholder"
:disabled="disabled"
:readonly="readonly"
:maxlength="maxlength > 0 ? maxlength : undefined"
class="vui-input__inner"
@focus="handleFocus"
@blur="handleBlur"
@input="displayValue = ($event.target as HTMLInputElement).value"
/>
<span v-if="showClearIcon" class="vui-input__clear" @click="handleClear">
×
</span>
<span v-if="showCount" class="vui-input__count">
{{ textLength }}{{ maxlength > 0 ? `/${maxlength}` : '' }}
</span>
</div>
</template>
<script setup lang="ts">
import { inputProps, inputEmits } from './types'
import { useInput } from './useInput'
const props = defineProps(inputProps)
const emit = defineEmits(inputEmits)
const {
isHovering, inputClasses, displayValue, textLength,
showClearIcon, handleFocus, handleBlur, handleClear
} = useInput(props, emit)
</script>
defineProps和defineEmits使用编译宏,无需import,在编译时自动处理。传入之前定义的props和emits对象,获得完整类型推断。
SCSS样式与CSS变量主题系统
/* src/components/Input/style.scss */
.vui-input {
display: inline-flex;
align-items: center;
width: 100%;
border: 1px solid var(--vui-border-color, #dcdfe6);
border-radius: var(--vui-border-radius, 4px);
background: var(--vui-bg-color, #fff);
transition: border-color 0.2s;
&--small { height: 24px; font-size: 12px; }
&--medium { height: 32px; font-size: 14px; }
&--large { height: 40px; font-size: 16px; }
&--focused {
border-color: var(--vui-primary-color, #409eff);
}
&--disabled {
background: var(--vui-disabled-bg, #f5f7fa);
cursor: not-allowed;
}
&__inner {
flex: 1;
border: none;
outline: none;
background: transparent;
padding: 0 12px;
height: 100%;
&:disabled { cursor: not-allowed; }
}
&__clear {
cursor: pointer;
padding: 0 8px;
color: var(--vui-text-placeholder, #c0c4cc);
&:hover { color: var(--vui-text-color, #606266); }
}
&__count {
font-size: 12px;
color: var(--vui-text-placeholder, #909399);
padding-right: 8px;
}
}
CSS变量方案使组件库支持运行时主题切换,用户只需覆盖 --vui-primary-color 等变量即可定制主题。响应式布局中通过媒体查询调整CSS变量值,实现自适应尺寸。
组件导出与install插件注册
// src/components/Input/index.ts
import Input from './Input.vue'
import type { App } from 'vue'
Input.install = (app: App) => {
app.component(Input.name || 'VuiInput', Input)
}
export { Input }
export default Input
// src/index.ts
import type { App } from 'vue'
import { Input } from './components/Input'
import { Button } from './components/Button'
import { Form } from './components/Form'
const components = [Input, Button, Form]
export { Input, Button, Form }
export default {
install(app: App) {
components.forEach(c => app.use(c))
}
}
每个组件带有install方法,支持全局注册和按需导入两种使用方式:
// 全局注册
import { createApp } from 'vue'
import VueUI from 'vue3-ui-lib'
const app = createApp(App)
app.use(VueUI)
// 按需导入(推荐,配合tree-shaking)
import { Input, Button } from 'vue3-ui-lib'
app.component('VuiInput', Input)
app.component('VuiButton', Button)
跨端小程序开发适配考虑
当组件库需要同时支持Web和小程序时,需分离DOM层和逻辑层。组合式函数天然可跨端复用,仅模板层需针对不同平台编写。以Taro或uni-app为例,将useInput逻辑保持不变,模板替换为对应平台组件:
// 逻辑层保持不变
import { useInput } from './useInput'
// Web模板
<input :value="displayValue" @input="..." />
// 小程序模板(条件编译)
<!-- #ifdef MP-WEIXIN -->
<input :value="displayValue" @input="..." />
<!-- #endif -->
Vue3组合式API的优势在于逻辑与视图解耦。同一套useForm、useInput、useValidation组合式函数可服务于Web、小程序、Flutter移动端的视图层,实现跨端逻辑复用。样式层通过平台条件编译切换CSS方案,保持逻辑一致性的同时尊重各平台差异。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/vue3-zu-he-shi-api-zu-jian-ku-she-ji-mo-shi-yu-typescript/