TypeScript支持增强
TypeScript 在 Vue.js 生态中的支持近年来显著增强,从核心库到周边工具链都提供了更完善的类型支持。开发者现在能够享受到更严格的类型检查、更智能的代码提示以及更流畅的开发体验。
TypeScript 与 Vue 3 的深度集成
Vue 3 从设计之初就考虑了对 TypeScript 的原生支持。与 Vue 2 需要通过装饰器或 class-component 实现类型支持不同,Vue 3 的 Composition API 天然适合类型推导。例如:
<script setup lang="ts">
import { ref } from 'vue'
// 自动推导为 Ref<number>
const count = ref(0)
// 明确指定类型
const user = ref<{ name: string; age: number }>({
name: 'Alice',
age: 25
})
function increment() {
count.value++ // 完全类型安全
}
</script>
这种集成带来了几个关键优势:
- 模板表达式自动类型检查
- 组件 props 的运行时类型验证
- 更好的 IDE 自动补全支持
组件 Props 的类型定义
Vue 3 提供了多种方式来定义组件 props 的类型:
import { defineComponent } from 'vue'
// 方式1:使用运行时声明
export default defineComponent({
props: {
title: String,
count: {
type: Number,
required: true
}
}
})
// 方式2:纯TypeScript类型
export default defineComponent({
props: {
title: {
type: String as PropType<'primary' | 'secondary'>,
default: 'primary'
},
items: {
type: Array as PropType<{ id: number; text: string }[]>,
required: true
}
}
})
// 方式3:结合泛型
interface User {
name: string
age: number
}
export default defineComponent({
props: {
user: {
type: Object as PropType<User>,
required: true
}
}
})
Composition API 的类型优势
Composition API 特别适合 TypeScript,因为它的函数式风格能更好地保留类型信息:
import { ref, computed } from 'vue'
interface Todo {
id: number
text: string
completed: boolean
}
export function useTodo() {
const todos = ref<Todo[]>([])
const completedCount = computed(() => {
return todos.value.filter(todo => todo.completed).length
})
function addTodo(text: string) {
todos.value.push({
id: Date.now(),
text,
completed: false
})
}
return {
todos,
completedCount,
addTodo
}
}
模板引用与组件实例类型
在模板中使用 ref 时,可以精确指定引用的类型:
<template>
<ChildComponent ref="childRef" />
<input ref="inputRef" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import ChildComponent from './ChildComponent.vue'
const childRef = ref<InstanceType<typeof ChildComponent>>()
const inputRef = ref<HTMLInputElement>()
onMounted(() => {
// 完全类型安全的方法调用
childRef.value?.someMethod()
// DOM元素类型安全
inputRef.value?.focus()
})
</script>
全局类型扩展
可以扩展 Vue 的全局类型声明以支持自定义选项:
// types/vue.d.ts
import { ComponentCustomProperties } from 'vue'
declare module 'vue' {
interface ComponentCustomProperties {
$filters: {
formatDate: (date: Date) => string
}
}
}
// 使用
const app = createApp({})
app.config.globalProperties.$filters = {
formatDate: (date: Date) => date.toLocaleDateString()
}
// 组件内使用
const formatted = vm.$filters.formatDate(new Date()) // 类型安全
与 Vue Router 的集成
Vue Router 4 提供了完整的 TypeScript 支持:
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/user/:id',
component: () => import('./views/User.vue'),
props: route => ({
id: Number(route.params.id), // 自动类型转换
query: route.query.search
})
}
]
})
// 在组件中使用
import { useRoute } from 'vue-router'
const route = useRoute()
// route.params.id 会被正确推断为 string | string[]
Pinia 状态管理的类型支持
Pinia 作为 Vue 的官方状态管理库,提供了出色的 TypeScript 体验:
import { defineStore } from 'pinia'
interface UserState {
name: string
age: number
preferences: {
theme: 'light' | 'dark'
notifications: boolean
}
}
export const useUserStore = defineStore('user', {
state: (): UserState => ({
name: '',
age: 0,
preferences: {
theme: 'light',
notifications: true
}
}),
actions: {
updateName(newName: string) {
this.name = newName // 完全类型安全
},
toggleTheme() {
this.preferences.theme =
this.preferences.theme === 'light' ? 'dark' : 'light'
}
},
getters: {
isAdult: (state) => state.age >= 18
}
})
// 在组件中使用
const store = useUserStore()
store.updateName('Alice') // 类型检查
工具链的改进
现代 Vue 工具链对 TypeScript 的支持也在不断提升:
- Vite 内置了快速的 TypeScript 编译
- Vitest 提供了完美的类型测试体验
- Volar 替代 Vetur 成为官方推荐的 IDE 扩展
// vitest 测试示例
import { mount } from '@vue/test-utils'
import MyComponent from './MyComponent.vue'
describe('MyComponent', () => {
it('renders correctly', () => {
const wrapper = mount(MyComponent, {
props: {
// 自动补全和类型检查
title: 'Test',
count: 1
}
})
expect(wrapper.text()).toContain('Test')
})
})
常见问题与解决方案
1. 第三方库类型缺失
对于没有类型定义的库,可以创建类型声明:
// shims.d.ts
declare module 'untyped-library' {
export function doSomething(options: {
foo: string
bar?: number
}): Promise<void>
}
2. 模板中的复杂表达式
对于复杂的模板表达式,可以使用计算属性保持类型安全:
<template>
<div>{{ formattedDate }}</div>
</template>
<script setup lang="ts">
import { computed } from 'vue'
const props = defineProps<{
date: Date
format?: 'short' | 'long'
}>()
const formattedDate = computed(() => {
return props.format === 'short'
? props.date.toLocaleDateString()
: props.date.toLocaleString()
})
</script>
3. 动态组件处理
使用 markRaw
和类型断言处理动态组件:
import { markRaw } from 'vue'
import type { Component } from 'vue'
const components: Record<string, Component> = {
home: markRaw(defineAsyncComponent(() => import('./Home.vue'))),
about: markRaw(defineAsyncComponent(() => import('./About.vue')))
}
const currentComponent = ref<Component>(components.home)
性能优化与类型安全
TypeScript 不仅提供类型安全,还能帮助优化性能:
// 使用常量枚举减少运行时代码
const enum Routes {
HOME = '/',
ABOUT = '/about'
}
// 使用字面量类型限制选项
type Theme = 'light' | 'dark' | 'system'
interface Settings {
theme: Theme
animations: boolean
}
const settings = reactive<Settings>({
theme: 'light',
animations: true
})
与 JSX/TSX 的结合使用
Vue 3 支持使用 TSX 编写组件,获得更好的类型支持:
import { defineComponent } from 'vue'
interface ButtonProps {
type?: 'primary' | 'danger'
onClick?: (event: MouseEvent) => void
}
const Button = defineComponent({
setup(props: ButtonProps, { slots }) {
return () => (
<button
class={`btn-${props.type || 'default'}`}
onClick={props.onClick}
>
{slots.default?.()}
</button>
)
}
})
// 使用
<Button type="primary" onClick={(e) => console.log(e)}>
Click me
</Button>
类型安全的依赖注入
Vue 的 provide/inject API 也支持类型安全:
import { inject, provide } from 'vue'
const ThemeSymbol = Symbol() as InjectionKey<'light' | 'dark'>
// 祖先组件
provide(ThemeSymbol, 'dark')
// 后代组件
const theme = inject(ThemeSymbol, 'light') // 默认值 'light'
高级类型模式
利用 TypeScript 的高级特性增强 Vue 开发:
// 条件类型
type ExtractComponentProps<T> = T extends new () => { $props: infer P }
? P
: never
// 映射类型
type OptionalProps<T> = {
[K in keyof T]?: T[K]
}
// 实用工具类型
interface BaseProps {
id: string
class?: string
}
type WithDefaults<T, D> = Omit<T, keyof D> & {
[K in keyof D]: K extends keyof T ? T[K] : never
}
function defineDefaults<T, D>(props: T, defaults: D): WithDefaults<T, D> {
return { ...defaults, ...props } as any
}
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn