TypeScript + Vue
约 664 字大约 2 分钟
布欧-Lewyon
2026-05-15
首页 › Vue › 工程化与架构 › TypeScript + Vue
Vue 3 是用 TypeScript 重写的,对 TS 的支持非常原生。
项目初始化
npm create vue@latest # 勾选 TypeScript组件 Props 类型
运行时声明 + 类型推导
<script setup lang="ts">
defineProps<{
title: string
count?: number
items: string[]
user?: {
name: string
age: number
}
}>()
</script>Props 默认值
<script setup lang="ts">
interface Props {
title: string
count?: number
items?: string[]
}
const props = withDefaults(defineProps<Props>(), {
count: 0,
items: () => []
})
</script>Emits 类型
<script setup lang="ts">
// 方式一:类型字面量
const emit = defineEmits<{
(e: 'update', id: number): void
(e: 'delete', id: number, name: string): void
}>()
// 方式二:Vue 3.3+ 简写
const emit = defineEmits<{
update: [id: number]
delete: [id: number, name: string]
}>()
function handleDelete(id: number) {
emit('delete', id, 'Alice')
}
</script>ref / reactive 类型
<script setup lang="ts">
import { ref, reactive, computed } from 'vue'
interface User {
id: number
name: string
role: 'admin' | 'editor' | 'viewer'
}
const count = ref<number>(0)
const user = ref<User | null>(null)
const state = reactive<{ users: User[] }>({ users: [] })
const total = computed<number>(() => state.users.length)
</script>模板 ref 类型
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const inputEl = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputEl.value?.focus() // 类型安全
})
</script>
<template>
<input ref="inputEl" />
</template>子组件 ref 类型
<!-- Child.vue -->
<script setup lang="ts">
import { ref } from 'vue'
const count = ref(0)
function reset() { count.value = 0 }
defineExpose<{ reset: () => void }>({ reset })
</script><!-- Parent.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import Child from './Child.vue'
const childRef = ref<InstanceType<typeof Child> | null>(null)
function handleReset() {
childRef.value?.reset() // ✅ 类型安全,能推导出 reset 方法
}
</script>
<template>
<Child ref="childRef" />
</template>事件处理类型
<script setup lang="ts">
function handleInput(event: Event) {
const target = event.target as HTMLInputElement
console.log(target.value)
}
function handleClick(event: MouseEvent) {
console.log(event.clientX, event.clientY)
}
</script>
<template>
<input @input="handleInput" />
<button @click="handleClick">点击</button>
</template>泛型组件
Vue 3.3+ 支持组件级别的泛型:
<script setup lang="ts" generic="T extends { id: number }">
defineProps<{
items: T[]
selected: T | null
}>()
const emit = defineEmits<{
select: [item: T]
}>()
</script>
<template>
<ul>
<li v-for="item in items" :key="item.id"
@click="emit('select', item)">
{{ item }}
</li>
</ul>
</template>使用:
<script setup lang="ts">
import List from './List.vue'
interface User { id: number; name: string }
/* 自动推导出 T = User */
</script>
<template>
<List :items="users" :selected="currentUser" @select="onSelect" />
</template>全局类型声明
// types/env.d.ts
/// <reference types="vite/client" />
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 扩展全局属性
declare module 'vue' {
interface ComponentCustomProperties {
$translate: (key: string) => string
}
}
export {}Composable 类型
// composables/useFetch.ts
import { ref, type Ref } from 'vue'
interface FetchState<T> {
data: Ref<T | null>
error: Ref<string | null>
loading: Ref<boolean>
}
export function useFetch<T>(url: string): FetchState<T> {
const data = ref<T | null>(null) as Ref<T | null>
const error = ref<string | null>(null)
const loading = ref(false)
// ...
return { data, error, loading }
}
// 使用
const { data: users } = useFetch<User[]>('/api/users')小结
| 场景 | 类型写法 |
|---|---|
| Props | defineProps<{ ... }>() + withDefaults |
| Emits | defineEmits<{ event: [arg: type] }>() |
| 模板 ref | ref<HTMLInputElement | null>(null) |
| 子组件 ref | ref<InstanceType<typeof Comp> | null>(null) |
| 事件 | event: Event / MouseEvent / KeyboardEvent |
| 泛型组件 | <script setup generic="T"> |
上一节:虚拟列表与响应式优化 下一节:Vite 高级配置与 Monorepo
