Vue3 + TypeScript的工程痛点在哪
Vue3的组合式API(Composition API)配合TypeScript,理论上能提供完整的类型推导。但实际项目里,defineProps泛型写法、组件事件类型、Provide/Inject的类型穿透、第三方库的类型补全,每一处都有暗坑。类型写不好,IDE提示一塌糊涂,重构时全程any警告满天飞。把TypeScript当成”加个ts后缀”是对工程效率的浪费,正确的做法是从组件设计阶段就把类型系统用起来。
defineProps的类型安全写法
Vue 3.3+推荐用泛型声明Props,比运行时声明更安全:
<script setup lang="ts">
// 泛型声明——完整类型推导
interface Props {
title: string
count?: number
items: Array<{ id: number; name: string; status: 'active' | 'inactive' }>
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
})
// props.title 自动推导为 string
// props.items 自动推导为完整类型
</script>
关键点:Props接口中的联合类型(如'active' | 'inactive')会被Vue编译器正确处理为运行时校验,不需要额外写validator。
defineEmits与组件事件的类型约束
事件类型是Vue3+TS最容易写any的地方。正确做法:
<script setup lang="ts">
// 声明事件签名——参数类型完整约束
const emit = defineEmits<{
(e: 'update', id: number, data: { name: string; value: number }): void
(e: 'delete', id: number): void
(e: 'change', payload: { field: string; oldValue: unknown; newValue: unknown }): void
}>()
// 调用时自动校验参数类型
emit('update', 1, { name: 'status', value: 100 }) // 类型正确
// emit('update', '1', {...}) // 编译报错
</script>
Provide/Inject的类型穿透方案
跨层级组件通信时,Provide/Inject默认拿不到类型。用InjectionKey泛型解决:
// types/injection-keys.ts
import type { InjectionKey, Ref } from 'vue'
export interface UserInfo {
id: number
name: string
role: 'admin' | 'editor' | 'viewer'
}
// 定义带类型的InjectionKey
export const USER_KEY: InjectionKey<Ref<UserInfo>> = Symbol('user')
export const THEME_KEY: InjectionKey<Ref<'light' | 'dark'>> = Symbol('theme')
// Provider组件
import { provide, ref } from 'vue'
const user = ref<UserInfo>({ id: 1, name: 'admin', role: 'admin' })
provide(USER_KEY, user)
// Injector组件
import { inject } from 'vue'
const user = inject(USER_KEY) // 自动推导为 Ref<UserInfo> | undefined
if (user) {
user.value.name // string类型
}
泛型组件:让组件复用性提升一个档次
表格、表单、下拉选择这类通用组件,数据类型应该由调用方决定。Vue3.3+支持泛型组件:
<!-- GenericSelect.vue -->
<script setup lang="ts" generic="T extends { id: number }">
interface Props {
options: T[]
modelValue?: T
labelKey: keyof T
}
const props = defineProps<Props>()
const emit = defineEmits<{
(e: 'update:modelValue', value: T): void
}>()
</script>
<template>
<select :value="modelValue?.id"
@change="emit('update:modelValue', ...)">
<option v-for="item in options" :key="item.id" :value="item.id">
{{ item[labelKey] }}
</option>
</select>
</template>
泛型组件让组件库的类型安全从”凑合用”变成”和原生TypeScript一样可靠”。
响应式API的类型避坑
reactive和ref的类型行为差异常踩坑:
// ref
const count = ref<number>(0)
count.value++ // 正确
// reactive
const state = reactive<{ name: string; list: number[] }>({
name: '',
list: []
})
state.name = 'test' // 正确
// 常见错误:reactive丢失数组类型
const list = reactive([]) // 推导为 any[],危险
const typedList = reactive<Product[]>([]) // 正确
// computed返回类型
const total = computed<number>(() => {
return state.list.reduce((sum, n) => sum + n, 0)
})
组件库设计的类型规范
做企业级组件库,类型规范比代码规范更基础:
- 所有Props必须用interface声明,禁止内联类型
- 事件回调参数类型必须完整声明,拒绝
(...args: any[]) => void - 组件暴露的方法用
defineExpose+ 显式类型 - 泛型组件的约束条件用
extends限定,不允许无约束的T - 第三方库缺失类型时,用
.d.ts手写声明,不要用@ts-ignore跳过
TypeScript在Vue3项目里的价值不是写法更好看,是让IDE成为第一道代码审查。类型写对了,重构改一个字段名,所有引用处自动标红;类型写错了或者到处any,重构就是手动排查的体力活。
原创文章,作者:小编,如若转载,请注明出处:https://www.yunthe.com/typescript-shi-zhan-jin-jie-vue3-zu-he-shi-api-xia-de-lei/