Vue + TypeScript 类型安全开发实战
一、环境搭建
npm create vue@latest
# 选择 TypeScript + ESLint + 组合式API
tsconfig.json 关键配置:
{
"compilerOptions": {
"strict": true,
"types": ["vite/client"],
"experimentalDecorators": true,
"useDefineForClassFields": true
}
}
二、组件类型安全
1. Props 类型定义
interface User {
id: number
name: string
email?: string
}
defineProps<{
user: User
isActive: boolean
// 带默认值需特殊处理
count?: number
}>()
withDefaults(defineProps<{
count?: number
}>(), {
count: 0
})
2. Emits 类型声明
const emit = defineEmits<{
(e: 'update:modelValue', value: string): void
(e: 'submit', payload: User): Promise<boolean>
}>()
三、组合式 API 类型增强
1. 响应式数据
const count = ref<number>(0) // 明确类型
const user = reactive<User>({
id: 1,
name: 'John'
})
// 自动推断
const double = computed(() => count.value * 2)
2. 自定义 Hook
// useFetch.ts
export function useFetch<T>(url: string) {
const data = ref<T | null>(null)
const error = ref<Error | null>(null)
const fetchData = async () => {
try {
const response = await fetch(url)
data.value = await response.json() as T
} catch (err) {
error.value = err as Error
}
}
return { data, error, fetchData }
}
// 组件中使用
const { data: posts } = useFetch<Post[]>('/api/posts')
四、TypeScript 5.0 新特性应用
1. 泛型参数常量
function identity<const T>(arg: T): T {
return arg
}
// 推导类型为 ["hello", 42] 而非 (string | number)[]
const result = identity(["hello", 42])
2. 装饰器元数据
@Reflect.metadata('design:type', Object)
class UserComponent {
@Track()
user: User = reactive(new User())
}
五、高级类型模式
1. 条件类型 Props
type Props<T extends 'text' | 'number'> = {
type: T
value: T extends 'text' ? string : number
}
defineProps<Props<'text'>>()
2. 类型安全的全局状态
// stores/counter.ts
export const useCounter = defineStore('counter', () => {
const count = ref(0)
const double = computed(() => count.value * 2)
function increment(step: number = 1) {
count.value += step
}
return { count, double, increment }
})
// 组件中使用
const counter = useCounter()
counter.increment(2) // 类型检查参数
六、最佳实践
- 严格模式:始终开启
strict: true - 类型推断:优先让 TypeScript 自动推断简单类型
- 泛型组件:使用
generic特性声明泛型组件
<script setup lang="ts" generic="T extends string | number">
defineProps<{
items: T[]
selected: T
}>()
</script>
- 类型导入:使用 Type-Only Imports
import type { Router } from 'vue-router'
七、常见问题解决
1. 模板引用类型
<script setup lang="ts">
import { ref } from 'vue'
import type { ElForm } from 'element-plus'
const formRef = ref<InstanceType<typeof ElForm>>()
</script>
<template>
<el-form ref="formRef"></el-form>
</template>
2. 动态组件类型
import { defineAsyncComponent } from 'vue'
const components = {
home: defineAsyncComponent(() => import('./Home.vue')),
profile: defineAsyncComponent(() => import('./Profile.vue'))
} as const // 关键类型断言
type ComponentKeys = keyof typeof components