阅读本文你能学到什么?
- 如何为
ref、reactive、computed标注类型- 如何为事件处理函数标注类型
- 如何为模板引用标注类型
- 如何处理对象的非空值场景
前言
前面五篇我们学完了 TypeScript 的核心语法------类型注解、接口、泛型、类型断言......学完之后,你可能会有疑惑:这些语法在 Vue 3 组件中怎么用?这一篇,就来看看。
一、Vue 3 + TS 项目环境
使用 Vue 官方脚手架创建项目时,直接选择 TypeScript 即可:
bash
npm create vue@latest
# 根据提示选择 TypeScript
创建完成后,项目会自动生成 tsconfig.json 配置文件,不需要手动配置。
💡 本文所有示例均基于
<script setup lang="ts">语法,这是 Vue 官方推荐的组合式 API 写法。💡 项目创建的具体步骤可参考 Vue 官方文档。
二、为什么需要标注类型
在 Vue 3 中为数据标注类型,核心好处有两个:
- 赋值时 TS 会校验数据类型是否匹配
- 使用时编辑器会提供准确的代码提示
看一个简单的对比:
typescript
import { reactive } from 'vue'
// ❌ 没有标注类型
const form = reactive({
username: '',
password: ''
})
form.username = 100 // ✅ 不报错,但 username 应该是 string
// ✅ 标注了类型
interface Form {
username: string
password: string
}
const form = reactive<Form>({
username: '',
password: ''
})
form.username = 100 // ❌ 报错:Type 'number' is not assignable to type 'string'
有了类型约束,赋值时的错误能被提前发现,编辑器也能给出准确的代码提示。如下图所示,输入 form. 时,编辑器会提示 form 的属性 username 和 password。

三、为 ref 标注类型
ref 和 TS 的配合通常分为两种方式:类型推导 和泛型指定类型。
3.1 类型推导(推荐简单数据)
如果初始值足够明确,TypeScript 可以自动推导出类型:
typescript
import { ref } from 'vue'
const count = ref(0) // 推导为 Ref<number>
const name = ref('Jack') // 推导为 Ref<string>
const isLoading = ref(false) // 推导为 Ref<boolean>
如果 ref 没有初始值,TypeScript 会自动推导为联合类型(包含 undefined),赋值时同样需要符合类型要求:
typescript
const age = ref<number>() // 推导为 Ref<number | undefined>
3.2 泛型指定类型(推荐复杂数据)
当类型为复杂类型,如联合类型(如 number | null)、对象、数组,用泛型指定类型:
typescript
import { ref } from 'vue'
// 联合类型
const year = ref<number | null>(null)
year.value = 2026 // ✅
year.value = '2026' // ❌ 报错:Type 'string' is not assignable to type 'number'
// 对象类型
interface User {
id: number
name: string
age: number
}
const user = ref<User | null>(null)
user.value = {
id: 1,
name: "张三",
age: 18
}
用泛型指定类型,不仅对 value 的赋值和初始值做了类型约束,使用时也有准确的代码提示(效果和第二章展示的类似)。

四、为 reactive 标注类型
reactive 和 TS 的配合通常也分为两种方式:类型推导 和泛型指定类型。
4.1 类型推导(推荐)
如果根据默认参数推导出的类型符合要求,直接使用类型推导即可:
typescript
import { reactive } from 'vue'
const state = reactive({
count: 0,
name: 'Jack'
})
// state 的类型为:{ count: number; name: string }
4.2 泛型指定类型(类型不匹配时使用)
如果默认对象推导出的类型不满足要求,可以使用泛型显式指定类型:
typescript
import { reactive } from 'vue'
interface State {
count: number
name: string
isLoading: boolean
}
const state = reactive<State>({
count: 0,
name: 'Jack',
isLoading: false
})
state.count = 10
state.isLoading = true
state.count = '10' // ❌ 报错:Type 'number' is not assignable to type 'string'
显式标注类型,不仅对属性赋值做了类型约束,reactive 的初始值也需要满足类型要求,使用时也有准确的代码提示,如下图所示。

五、为 computed 标注类型
计算属性由已知的响应式数据计算得到,依赖的数据类型一旦确定,TypeScript 会自动推导出 计算属性的类型。因此,多数情况下不需要手动标注类型,TS 会提供准确的代码提示。
typescript
const count = ref(0) // 推导为 Ref<number>
const doubleCount = computed(() => {
return count.value * 2
}) // 推导为 ComputedRef<number>
如果计算逻辑较复杂,或者需要确保返回类型符合预期,也可以显式指定:
typescript
interface Product {
id: string
name: string
price: number
}
const products = ref<Product[]>([
{ id: '1001', name: '男鞋', price: 299 },
{ id: '1002', name: '女鞋', price: 205 },
{ id: '1003', name: '衬衫', price: 99 }
])
// 计算总价(显式指定类型)
const totalPrice = computed<number>(() => {
return products.value.reduce((sum, item) => sum + item.price, 0)
})
// 过滤价格大于 200 的商品(自动推导)
const expensiveProducts = computed(() => {
return products.value.filter(item => item.price > 200)
})
六、为事件处理函数标注类型
原生 DOM 事件处理函数的参数默认会被推断为 any 类型,没有任何类型提示。
html
<template>
<input type="text" @change="inputChange" />
</template>
<script setup lang="ts">
const inputChange = (e) => {
// e 自动推断为 any 类型
}
</script>

想要有良好的类型提示,我们需要手动标注类型,事件处理函数的类型标注主要做两件事:
① 标注事件对象类型
给事件对象形参 e 标注为 Event 类型,可以获得事件对象的相关类型提示:
typescript
const inputChange = (e: Event) => {
// 获得事件对象的类型提示
}

② 使用断言获取更精确的类型
如果需要更精确的 DOM 类型提示,可以使用断言(as)进行操作:
typescript
const inputChange = (e: Event) => {
const target = e.target as HTMLInputElement // 获取更精确的 DOM 类型
}

七、为模板引用标注类型
7.1 如何进行类型标注
通过具体的 DOM 类型联合 null 作为泛型参数。比如我们想获取一个 input DOM 元素:
html
<template>
<input ref="inputRef" />
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
// 使用可选链防止类型错误
inputRef.value?.focus()
})
</script>
说明:
- 泛型参数中通过将获取的 DOM 实际类型联合
null进行类型标注 - 组件完全挂载完毕之前 ref 的值都是
null,所以访问ref.value时需要使用可选链防止类型错误
7.2 常见 DOM 元素类型
| 元素类型 | TypeScript 类型 |
|---|---|
<div> |
HTMLDivElement |
<span> |
HTMLSpanElement |
<input> |
HTMLInputElement |
<button> |
HTMLButtonElement |
<a> |
HTMLAnchorElement |
<form> |
HTMLFormElement |
<img> |
HTMLImageElement |
八、对象的非空值处理
8.1 空值场景说明
当对象的属性可能是 null 或 undefined 时,我们称之为"空值"。直接访问空值上的属性或方法,TypeScript 会报类型错误。
比如 inputRef 在组件挂载完毕之前,value 还是 null,如果直接调用 focus(),TS 就会报错:
typescript
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputRef.value.focus() // ❌ 对象可能为 "null"
})

8.2 可选链方案(推荐)
可选链 ?. 是一种安全访问嵌套对象属性的方式。当可选链前面的值为 undefined 或 null 时,它会停止运算并返回 undefined,不会报错:
typescript
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputRef.value?.focus() // 可选链安全访问
})
8.3 逻辑判断方案
通过逻辑判断,只有确认值存在时才继续访问属性:
typescript
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
if (inputRef.value) {
inputRef.value.focus() // 逻辑判断确认存在
}
})
8.4 非空断言方案(谨慎使用)
非空断言(!)用于告诉 TypeScript:开发者明确知道当前的值一定不是 null 或 undefined,让 TS 通过类型校验:
typescript
const inputRef = ref<HTMLInputElement | null>(null)
onMounted(() => {
inputRef.value!.focus() // 非空断言,仅在确定有值时使用
})
⚠️ 注意 :使用非空断言要格外小心。它没有实际的 JS 判断逻辑,只是通过了 TS 的类型校验。如果实际运行时值为
null,程序会直接报错。
小结
| 知识点 | 示例 | 说明 |
|---|---|---|
| ref 类型注解 | `ref<User | null>(null)` |
| reactive 类型注解 | reactive<State>({ ... }) |
类型不匹配时用泛型指定类型 |
| computed 类型注解 | computed<number>(() => ...) |
指定返回值类型 |
| 事件处理函数 | e: Event |
标注事件对象类型,获得类型提示 |
| 模板引用 | `ref<HTMLInputElement | null>(null)` |
| 可选链 | inputRef.value?.focus() |
安全访问可能为空的值 |
| 非空断言 | inputRef.value!.focus() |
仅在确定值不为空时使用,需谨慎 |
下篇预告
📖 上篇回顾:TypeScript 高级特性 ------ 类型断言与泛型
下一篇将进入 Vue 3 组件通信与类型声明,学习如何为 props 和 emits 标注类型,以及类型声明文件(.d.ts)的用法。
欢迎持续关注 🚀