TypeScript 核心语法应用 —— Vue 3 中的使用(上)

阅读本文你能学到什么?

  • 如何为 refreactivecomputed 标注类型
  • 如何为事件处理函数标注类型
  • 如何为模板引用标注类型
  • 如何处理对象的非空值场景

前言

前面五篇我们学完了 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 中为数据标注类型,核心好处有两个:

  1. 赋值时 TS 会校验数据类型是否匹配
  2. 使用时编辑器会提供准确的代码提示

看一个简单的对比:

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 的属性 usernamepassword

三、为 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 空值场景说明

当对象的属性可能是 nullundefined 时,我们称之为"空值"。直接访问空值上的属性或方法,TypeScript 会报类型错误。

比如 inputRef 在组件挂载完毕之前,value 还是 null,如果直接调用 focus(),TS 就会报错:

typescript 复制代码
const inputRef = ref<HTMLInputElement | null>(null)

onMounted(() => {
  inputRef.value.focus()  // ❌ 对象可能为 "null"
})

8.2 可选链方案(推荐)

可选链 ?. 是一种安全访问嵌套对象属性的方式。当可选链前面的值为 undefinednull 时,它会停止运算并返回 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:开发者明确知道当前的值一定不是 nullundefined,让 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)的用法。

欢迎持续关注 🚀

参考资料

相关推荐
No Silver Bullet2 小时前
Vue进阶(贰幺叁)vue.config.js 中 productionSourceMap 作用详解
前端·javascript·vue.js
xcsweb2 小时前
从5分钟到10秒:我用一个Skill把团队部署效率提升了30倍
前端·vue.js
用户62960593247052 小时前
一次位置调整引发的画布失忆:深入 Vue2 虚拟 DOM 复用机制
前端·vue.js
Sterting2 小时前
条件渲染与列表渲染
前端·vue.js
__sjfzllv___3 小时前
在职前端Leader学习/转行 AI Agent -DAY28
前端
不爱说话郭德纲3 小时前
uni-app x iOS 扫码模糊怎么办?AVFoundation 灰尘级别摄像头助你超广角聚焦
前端·uni-app·app
柒和远方3 小时前
V062:WebGPU 端侧推理与单例模式:从 Transformers.js 把大模型装进浏览器,到 Worker 线程的流式对话
前端
huabuyu3 小时前
INP 自动化归因与自愈:如何自动定位、修复并验证一次卡顿
前端·javascript
何时梦醒3 小时前
第一篇:项目概览 — 在浏览器里跑大模型,端侧 AI 的革命来了
前端·人工智能