组件类型定义
介绍
组件类型定义是 Vue 3 TypeScript 项目中确保类型安全的核心部分。所有业务组件都使用 TypeScript 编写,为 Props、Emits、Slots 等提供完整的类型定义。
核心特性:
- 类型安全 - 通过 TypeScript 接口定义组件 Props
- 智能提示 - IDE 提供完整的属性提示和自动补全
- 文档化注释 - 使用 JSDoc 注释为属性添加说明
- 泛型支持 - 支持泛型组件,实现灵活的类型推导
- 默认值定义 - 使用
withDefaults为 Props 提供默认值
基础组件类型
Icon 图标组件
typescript
/** Icon 图标组件 Props */
interface IconProps {
/** 图标值(字符串形式) */
value?: string
/** 图标代码(IconCode 类型) */
code?: IconCode
/** 图标尺寸 */
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl' | string | number
/** 图标颜色 */
color?: string
/** 图标动画效果 */
animate?: 'shake' | 'rotate180' | 'moveUp' | 'expand' | 'shrink' | 'breathing'
}
/** 图标代码类型 */
interface IconCode {
/** 图标类型: font-字体图标, uno-UnoCSS图标 */
type: 'font' | 'uno'
/** 图标名称 */
name: string
}IconSelect 图标选择器
typescript
/** IconSelect 图标选择器 Props */
interface IconSelectProps {
/** 绑定值(v-model) */
modelValue: string
/** 选择器宽度 */
width?: string
/** 是否禁用 */
disabled?: boolean
/** 占位符文本 */
placeholder?: string
/** 是否可清空 */
clearable?: boolean
}
/** IconSelect 图标选择器事件 */
interface IconSelectEmits {
(e: 'update:modelValue', value: string): void
(e: 'select', value: string): void
(e: 'clear'): void
}表单组件类型
AFormInput 输入框组件
typescript
/** 输入组件的Props接口定义 */
interface AFormInputProps {
/** 绑定值 */
modelValue?: string | number | null | undefined
/** 标签文本 */
label?: string
/** 标签宽度 */
labelWidth?: number | string
/** 占位符文本 */
placeholder?: string
/** 组件宽度 */
width?: number | string
/** 表单域model数据字段名 */
prop?: string
/** 最大长度 */
maxlength?: number | string
/** 是否显示字数统计 */
showWordLimit?: boolean
/** 是否显示表单项 */
showFormItem?: boolean
/** 是否显示密码可见性切换按钮 */
showPassword?: boolean
/** 输入框类型 */
type?: 'text' | 'textarea' | 'number' | 'password'
/** 文本域自适应配置 */
autosize?: { minRows?: number; maxRows?: number }
/** 组件尺寸 */
size?: '' | 'default' | 'small' | 'large'
/** 栅格占据的列数 */
span?: SpanType
/** 是否显示清除按钮 */
clearable?: boolean
/** 是否禁用 */
disabled?: boolean
/** 文本域行数 */
rows?: number
/** 提示信息 */
tooltip?: string
/** 数字输入框最小值 */
min?: number
/** 数字输入框最大值 */
max?: number
/** 数字输入框步长 */
step?: number
/** 是否只能输入 step 的倍数 */
stepStrictly?: boolean
/** 数值精度 */
precision?: number
/** 是否使用控制按钮 */
controls?: boolean
/** 控制按钮位置 */
controlsPosition?: '' | 'right'
/** 响应式模式 */
responsiveMode?: 'screen' | 'container' | 'modal-size'
/** 模态框尺寸 */
modalSize?: 'small' | 'medium' | 'large' | 'xl'
/** 是否防止浏览器自动填充 */
preventAutofill?: boolean
}
/** AFormInput 组件事件定义 */
interface AFormInputEmits {
(e: 'update:modelValue', value: string | number | null | undefined): void
(e: 'input', value: string | number | null | undefined): void
(e: 'blur', value: any): void
(e: 'change', value: any): void
(e: 'enter', value: any): void
}AFormSelect 下拉选择组件
typescript
/** 下拉选择组件 Props */
interface AFormSelectProps {
/** 绑定值 */
modelValue?: string | number | boolean | object | any[]
/** 标签文本 */
label?: string
/** 标签宽度 */
labelWidth?: number | string
/** 占位符 */
placeholder?: string
/** 表单字段名 */
prop?: string
/** 选项数据 */
options?: SelectOption[]
/** 是否多选 */
multiple?: boolean
/** 是否可清空 */
clearable?: boolean
/** 是否可搜索 */
filterable?: boolean
/** 是否禁用 */
disabled?: boolean
/** 栅格占据的列数 */
span?: SpanType
/** 是否显示表单项 */
showFormItem?: boolean
/** 组件尺寸 */
size?: '' | 'default' | 'small' | 'large'
/** 提示信息 */
tooltip?: string
/** 是否允许用户创建新条目 */
allowCreate?: boolean
/** 是否折叠多选标签 */
collapseTags?: boolean
/** 多选时最多显示的标签数量 */
maxCollapseTags?: number
}
/** 选项数据类型 */
interface SelectOption {
/** 显示文本 */
label: string
/** 选项值 */
value: string | number | boolean
/** 是否禁用此选项 */
disabled?: boolean
/** 选项分组 */
options?: SelectOption[]
}AFormFileUpload 文件上传组件
typescript
/** 文件上传组件 Props */
interface AFormFileUploadProps {
/** 绑定值,文件列表 */
modelValue?: string | UploadFile[]
/** 标签文本 */
label?: string
/** 表单字段名 */
prop?: string
/** 最大上传数量 */
limit?: number
/** 文件大小限制(MB) */
fileSize?: number
/** 允许的文件类型 */
fileType?: string[]
/** 是否显示文件列表 */
showFileList?: boolean
/** 是否禁用 */
disabled?: boolean
/** 栅格占据的列数 */
span?: SpanType
/** 上传按钮文字 */
buttonText?: string
/** 提示文字 */
tip?: string
/** 是否自动上传 */
autoUpload?: boolean
}
/** 上传文件类型 */
interface UploadFile {
name: string
url: string
size?: number
type?: string
status?: 'uploading' | 'success' | 'error'
percentage?: number
uid?: number
raw?: File
}
/** 文件上传组件事件 */
interface AFormFileUploadEmits {
(e: 'update:modelValue', value: string | UploadFile[]): void
(e: 'success', file: UploadFile, fileList: UploadFile[]): void
(e: 'error', error: Error, file: UploadFile): void
(e: 'remove', file: UploadFile, fileList: UploadFile[]): void
(e: 'exceed', files: File[], fileList: UploadFile[]): void
}业务组件类型
AModal 模态框组件
typescript
/** AModal 组件属性接口定义 */
interface AModalProps {
/** 控制模态框显示/隐藏状态 */
modelValue: boolean
/** 模态框模式 */
mode?: 'dialog' | 'drawer'
/** 模态框标题 */
title?: string
/** 自定义宽度/尺寸 */
width?: string | number
/** 预设尺寸 */
size?: 'small' | 'medium' | 'large' | 'xl'
/** 是否显示关闭按钮 */
closable?: boolean
/** 是否可以通过点击遮罩层关闭 */
maskClosable?: boolean
/** 是否可以通过 ESC 键关闭 */
keyboard?: boolean
/** 关闭时是否销毁内部元素 */
destroyOnClose?: boolean
/** 是否将模态框挂载到 body 元素下 */
appendToBody?: boolean
/** 关闭前的回调函数 */
beforeClose?: (done: () => void) => void
/** 是否可以拖动(仅对话框模式) */
movable?: boolean
/** 抽屉弹出方向 */
direction?: 'ltr' | 'rtl' | 'ttb' | 'btt'
/** 是否显示底部操作区域 */
showFooter?: boolean
/** 底部按钮类型 */
footerType?: 'default' | 'close-only'
/** 底部按钮对齐方式 */
footerAlign?: 'left' | 'center' | 'right'
/** 内容区域是否显示加载状态 */
loading?: boolean
/** 是否全屏显示 */
fullscreen?: boolean
/** 确认按钮文本 */
confirmText?: string
/** 取消按钮文本 */
cancelText?: string
}
/** AModal 组件事件定义 */
interface AModalEmits {
(e: 'update:modelValue', value: boolean): void
(e: 'confirm'): void
(e: 'cancel'): void
(e: 'open'): void
(e: 'opened'): void
(e: 'close'): void
(e: 'closed'): void
}AOssMediaManager OSS 媒体管理器
typescript
/** OSS 媒体管理器组件 Props */
interface AOssMediaManagerProps {
/** 绑定值,选中的文件URL或URL数组 */
modelValue?: string | string[]
/** 是否多选模式 */
multiple?: boolean
/** 最大选择数量(多选模式下有效) */
limit?: number
/** 文件类型过滤 */
accept?: string[]
/** 是否显示上传按钮 */
showUpload?: boolean
/** 是否显示删除按钮 */
showDelete?: boolean
/** 是否禁用 */
disabled?: boolean
}泛型组件类型
通用表格组件
typescript
/**
* 通用表格组件 Props
* @template T 表格数据类型
*/
interface TableProps<T = any> {
/** 表格数据(分页结果) */
data: PageResult<T>
/** 字段配置 */
fields: FieldConfig[]
/** 是否加载中 */
loading?: boolean
/** 是否显示选择框 */
selection?: boolean
/** 是否显示序号列 */
showIndex?: boolean
/** 表格高度 */
height?: string | number
/** 是否显示边框 */
border?: boolean
/** 是否显示斑马纹 */
stripe?: boolean
/** 表格尺寸 */
size?: 'large' | 'default' | 'small'
}
/** 分页结果类型 */
interface PageResult<T> {
rows: T[]
total: number
}
/** 字段配置类型 */
interface FieldConfig {
prop: string
label: string
width?: string | number
minWidth?: string | number
fixed?: 'left' | 'right'
align?: 'left' | 'center' | 'right'
sortable?: boolean
formatter?: (row: any, column: any, cellValue: any) => string
}
/**
* 通用表格组件事件
* @template T 表格数据类型
*/
interface TableEmits<T> {
(e: 'select', rows: T[]): void
(e: 'row-click', row: T): void
(e: 'update:query', query: PageQuery): void
(e: 'sort-change', column: any, prop: string, order: string): void
}通用表单组件
typescript
/**
* 通用表单组件 Props
* @template T 表单数据类型
*/
interface FormProps<T = any> {
/** 表单数据 */
modelValue: T
/** 表单模式 */
mode: 'add' | 'edit' | 'view'
/** 表单配置 */
config: FormConfig[]
/** 表单验证规则 */
rules?: FormRules
/** 表单标签宽度 */
labelWidth?: string | number
/** 表单尺寸 */
size?: 'large' | 'default' | 'small'
/** 是否禁用所有表单项 */
disabled?: boolean
/** 是否显示必填星号 */
showRequiredAsterisk?: boolean
}
/** 表单配置类型 */
interface FormConfig {
prop: string
label: string
component: 'input' | 'select' | 'date' | 'upload' | 'cascader'
span?: number
componentProps?: Record<string, any>
hidden?: boolean
}
type FormRules = Record<string, FormRule[]>
interface FormRule {
required?: boolean
message?: string
trigger?: 'blur' | 'change'
min?: number
max?: number
pattern?: RegExp
validator?: (rule: any, value: any, callback: any) => void
}响应式类型定义
SpanType 响应式栅格类型
typescript
/**
* 响应式栅格跨度类型
* 支持三种形式:
* 1. 数字: 固定跨度
* 2. 字符串: 预设配置 'auto'
* 3. 响应式对象: 不同屏幕尺寸使用不同跨度
*/
type SpanType =
| number
| 'auto'
| {
xs?: number // 超小屏 <768px
sm?: number // 小屏 ≥768px
md?: number // 中屏 ≥992px
lg?: number // 大屏 ≥1200px
xl?: number // 超大屏 ≥1920px
}使用示例
vue
<template>
<!-- 固定跨度 -->
<AFormInput label="用户名" v-model="form.userName" :span="12" />
<!-- 预设响应式 -->
<AFormInput label="邮箱" v-model="form.email" span="auto" />
<!-- 自定义响应式 -->
<AFormInput
label="手机号"
v-model="form.phone"
:span="{ xs: 24, sm: 24, md: 12, lg: 8, xl: 6 }"
/>
</template>ResponsiveMode 响应式模式
typescript
/** 响应式模式类型 */
type ResponsiveMode =
| 'screen' // 基于屏幕尺寸(默认)
| 'container' // 基于容器尺寸(弹窗场景推荐)
| 'modal-size' // 基于 AModal 的 size 属性
/** 模态框尺寸类型 */
type ModalSize = 'small' | 'medium' | 'large' | 'xl'Expose 暴露方法类型
typescript
/** 表单组件暴露的方法 */
interface FormExpose {
/** 验证表单 */
validate: () => Promise<boolean>
/** 验证指定字段 */
validateField: (props: string[]) => Promise<boolean>
/** 重置表单 */
resetFields: () => void
/** 清空验证结果 */
clearValidate: () => void
/** 滚动到指定字段 */
scrollToField: (prop: string) => void
}使用组件实例类型
vue
<template>
<BasicForm ref="formRef" v-model="form" :config="formConfig" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { FormExpose } from '@/components'
const formRef = ref<FormExpose>()
const handleSubmit = async () => {
const valid = await formRef.value?.validate()
if (valid) {
console.log('验证通过')
}
}
</script>最佳实践
1. 使用 interface 而非 type
typescript
// ✅ 推荐 - interface 可以扩展
interface ButtonProps {
type?: ButtonType
size?: ButtonSize
}
// ❌ 不推荐
type ButtonProps = {
type?: ButtonType
size?: ButtonSize
}2. 为所有 Props 添加 JSDoc 注释
typescript
interface FormInputProps {
/**
* 输入框类型
* @default 'text'
*/
type?: 'text' | 'password' | 'number'
/**
* 最大长度
* @default 255
*/
maxlength?: number
}3. 使用 withDefaults 提供默认值
typescript
const props = withDefaults(defineProps<FormInputProps>(), {
type: 'text',
maxlength: 255,
clearable: true,
disabled: false
})4. 事件定义使用元组语法
typescript
// ✅ 推荐 - 简洁清晰
const emit = defineEmits<{
'update:modelValue': [value: string]
submit: [data: FormData]
cancel: []
}>()5. 泛型组件使用 script generic
vue
<script setup lang="ts" generic="T extends Record<string, any>">
interface TableProps {
data: T[]
fields: FieldConfig[]
}
const props = defineProps<TableProps>()
const emit = defineEmits<{
select: [rows: T[]]
'row-click': [row: T]
}>()
</script>6. 使用联合类型而非字符串
typescript
// ✅ 推荐 - 类型安全
interface ButtonProps {
type?: 'primary' | 'success' | 'warning' | 'danger' | 'info'
size?: 'large' | 'default' | 'small'
}
// ❌ 不推荐 - 没有类型检查
interface ButtonProps {
type?: string
size?: string
}常见问题
1. Props 默认值设置后仍然是 undefined
原因: 没有使用 withDefaults,或默认值是对象/数组需要使用工厂函数
解决:
typescript
const props = withDefaults(defineProps<{
options?: SelectOption[]
config?: FormConfig
}>(), {
options: () => [], // 数组使用工厂函数
config: () => ({}) // 对象使用工厂函数
})2. 泛型组件类型推导失败
原因: 没有使用 Vue 3.3+ 的 generic 语法
解决:
vue
<script setup lang="ts" generic="T extends Record<string, any>">
interface TableProps {
data: T[]
}
const props = defineProps<TableProps>()
</script>3. 响应式 Props 在 computed 中丢失响应性
原因: 直接解构 Props
解决:
typescript
const props = defineProps<FormProps>()
// ❌ 错误 - 直接解构会丢失响应性
const { label, disabled } = props
// ✅ 正确 - 在 computed 中访问
const isDisabled = computed(() => props.disabled || props.mode === 'view')
// ✅ 正确 - 使用 toRef
const label = toRef(props, 'label')4. 组件 ref 类型不正确
原因: 使用了错误的实例类型
解决:
vue
<script setup lang="ts">
// ✅ 正确 - 使用 expose 的接口类型
import type { FormExpose } from '@/components'
const formRef = ref<FormExpose>()
// 或使用 InstanceType
const formRef = ref<InstanceType<typeof BasicForm>>()
</script>5. Props 验证器中的类型问题
原因: 使用运行时验证而非类型系统
解决:
typescript
// ✅ 正确 - 使用 TypeScript 类型定义
interface Props {
status?: 'active' | 'inactive' | 'pending'
}
const props = withDefaults(defineProps<Props>(), {
status: 'pending'
})
// 不需要 validator,类型系统已经保证了类型安全总结
组件类型定义核心要点:
- interface 优先 - 使用 interface 定义 Props,便于扩展
- JSDoc 注释 - 为所有属性添加详细注释
- withDefaults - 使用 withDefaults 提供默认值
- 泛型支持 - 使用 generic 语法支持泛型组件
- 联合类型 - 使用联合类型替代字符串提升类型安全
- 响应式类型 - SpanType 支持多种响应式配置方式
- Expose 类型 - 为暴露方法定义明确类型
