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

阅读本文你能学到什么?

  • 如何为组件的 props 标注类型
  • 如何为组件的 emits 标注类型
  • 类型声明文件(.d.ts)的用途与用法

前言

上一篇我们学会了 Vue 3 组件内部状态和逻辑的 TS 用法------refreactivecomputed、事件处理、模板引用等。但一个组件很少是独立存在的,组件之间总要传数据。

这一篇,就来看看在 <script setup lang="ts"> 中,如何用 TS 写出类型安全的组件通信,以及类型声明文件怎么用。

一、为 props 标注类型

defineProps 用于声明组件接收的 props,标注类型后,既能保证外部传入的数据类型正确,也能在组件内部使用和外部传递时获得代码提示

1.1 基础用法

语法defineProps<Props类型>()

示例 :按钮组件MyButton.vue有两个 prop 参数,color 类型为 string 且为必填,size 类型为 string 且为可选:

html 复制代码
<script setup lang="ts">
interface Props {
  color: string    // 必填
  size?: string    // 可选
}

const props = defineProps<Props>()
</script>

使用时,必须传递 color 属性,size 为可选:

html 复制代码
<template>
  <!-- ✅ 正确 -->
  <MyButton color="primary" />
  <!-- ✅ 正确 -->
  <MyButton color="primary" size="large" />
  <!-- ❌ 错误:缺少必填属性 color -->
  <MyButton size="large" />
</template>

1.2 设置默认值

当使用泛型语法声明 props 时,默认值无法直接在类型定义中指定。Vue 提供了两种写法来设置默认值,你需要根据 Vue 版本选择合适的方式。

方式一:响应式 Props 解构(Vue 3.5+ 推荐)

从 Vue 3.5 开始,可以直接在解构时设置默认值,代码最简洁:

html 复制代码
<script setup lang="ts">
interface Props {
  msg?: string
  labels?: string[]
}

// 直接在解构时赋默认值
const { msg = 'hello', labels = ['one', 'two'] } = defineProps<Props>()
</script>

💡 Vue 3.5 对 Props 解构做了增强,解构后的变量仍然保持响应式,不需要额外处理。

方式二:withDefaults 编译器宏(Vue 3.4 及以下)

在 Vue 3.4 及更低版本中,需要使用 withDefaults 来设置默认值:

html 复制代码
<script setup lang="ts">
interface Props {
  msg?: string
  labels?: string[]
}

const props = withDefaults(defineProps<Props>(), {
  msg: 'hello',
  labels: () => ['one', 'two']  // ⚠️注意: 数组、对象需要用函数包装
})
</script>

withDefaults 不仅提供默认值,还会对默认值进行类型检查,并自动移除已设默认值属性的可选标志。

⚠️ 注意 :使用 withDefaults 时,数组、对象等引用类型必须用函数返回(() => \[\]),否则所有组件实例会共享同一份数据;且 props 不支持解构,需直接使用 props.xxx 访问。Vue 3.5+ 的响应式 Props 解构则无此限制。

二、为 emits 标注类型

defineEmits 用于声明组件可以触发的自定义事件,标注类型后,既能约束事件名称并给出自动提示,避免拼写错误,也能约束传参类型,防止参数类型错误。

2.1 基础用法

语法defineEmits<Emits类型>()

示例 :子组件触发一个名称为 'change' 的事件,并且传递一个类型为 string 的参数:

html 复制代码
<script setup lang="ts">
// 1. 定义事件类型 Emits
interface Emits {
  (e: 'change', state: string): void
}

// 2. 给泛型参数传参
const emit = defineEmits<Emits>()

const changeState = () => {
  emit('change', 'pending') // ✅ 类型匹配
  // emit('change', 123) // ❌ 报错:参数类型不匹配
  // emit('change-event', 'handled')  // ❌ 报错:事件名称不匹配
}
</script>

2.2 多个事件

如果有多个事件,可以在 Emits 接口中继续定义,每个事件的参数数量和类型可以不同:

html 复制代码
<script setup lang="ts">
interface Emits {
  (e: 'change', state: string): void                               // 1 个参数
  (e: 'submit', username: string, password: string): void          // 2 个参数
  (e: 'update', id: number, data: object, timestamp: number): void // 3 个参数
}

const emit = defineEmits<Emits>()
</script>

💡 补充: Vue 3.3+ 还支持更简洁的元组语法,和上面的调用签名语法效果相同:

html 复制代码
<script setup lang="ts">
const emit = defineEmits<{
  'change': [state: string]
  'submit': [username: string, password: string]
  'update': [id: number, data: object, timestamp: number]
}>()
</script>

三、类型声明文件 .d.ts

3.1 什么是类型声明文件

在 TS 中以 .d.ts 为后缀的文件就是类型声明文件,主要作用是为 JS 模块提供类型信息支持,从而获得类型提示。

比如 axios 本身是 JS 编写的,但我们在写 axios. 时依然有代码提示,这是因为 axios 的安装包中包含了 index.d.ts 类型声明文件。

.d.ts 文件会在你使用 JS 模块时被 TS 自动加载,从而提供类型提示。如果库本身是使用 TS 编写的,在打包的时候经过配置自动生成对应的 .d.ts 文件。

3.2 使用 DefinitelyTyped 提供类型声明文件

场景 :有些库本身并不是采用 TS 编写的,无法直接生成配套的 .d.ts 文件 ,如果也想获得类型提示,就需要 DefinitelyTyped 提供类型声明文件。

例如使用 jQuery:

typescript 复制代码
import jquery from 'jquery'
// ❌ 报错:无法找到模块"jquery"的声明文件

DefinitelyTyped 是一个 TS 类型定义的仓库,专门为 JS 编写的库提供类型声明(@types/库名)。比如为 jQuery 提供类型提示可以安装 @types/jquery

bash 复制代码
npm install @types/jquery
typescript 复制代码
import jquery from 'jquery'
// ✅ 现在有类型提示了

补充: 如何确定某个库是否受 DefinitelyTyped 支持呢?

推荐以下两种查询方式:

1.官方搜索工具:访问 TypeScript Type Search,输入库名即可快速查看是否存在对应的 @types 包及其版本信息。

  1. npm 命令行查询:在终端执行 npm search @types/<库名>,若返回有效结果,则说明该库已有社区维护的类型声明。

3.3 TS 内置类型声明文件

TS 为 JS 运行时可用的所有标准化内置 API 都提供了声明文件。这些文件既不需要编译生成,也不需要三方提供。

比如 lib.es5.d.ts 以及 lib.dom.d.ts 都是内置的类型声明文件,为原生 JS 和浏览器 API 提供类型提示。

鼠标放到every按住ctrl键,就可以看到如下图内置ts文件的位置:

3.4 自定义类型声明文件

.d.ts 文件在项目中可以进行自定义创建,通常有两种作用:

场景一:TS 类型复用(重要)

在项目中,多个组件可能需要使用同一个类型定义(比如 User 接口)。如果每个组件都重复定义一遍,不仅冗余,维护起来也很麻烦。通过 .d.ts 文件,可以把类型定义抽离出来,供多个组件复用。

src/types/index.d.ts 中定义类型:

typescript 复制代码
export interface User {
  id: string
  name: string
  age: number
}

在组件中直接导入使用:

html 复制代码
<script setup lang="ts">
  //注意:实际导入时需根据文件位置调整路径,示例中类型文件位于 src/types/index.d.ts
  import type { User } from "@/types/index";
  const userInfo: User = {
    id: "1",
    name: "张三",
    age: 18
  }; // ✅ 类型校验通过,有完整的智能提示
  const userInfo2: User = {}; //❌ 报错:类型"{}"缺少类型"User"中的以下属性: id, name, age
</script>

为了区分普通模块,强烈推荐 加上 type 关键词。

场景二:给 JS 文件提供类型(了解)

通过 declare 关键词可以为 JS 文件中的变量声明对应类型,这样 JS 导出的模块在使用的时候也会获得类型提示:

💡 .d.ts 文件需要和对应的 .js 文件保持同名(仅后缀不同),TS 才会自动关联类型声明。

例如,在一个文件夹内创建3个文件index.d.tsindex.jsmain.vue

  • index.d.ts
typescript 复制代码
type MathType = (a: number, b: number) => number;
declare const add: MathType;
declare const sub: MathType;
export { add, sub };
  • index.js
typescript 复制代码
export const add = (a, b) => {
  return a + b;
};
export const sub = (a, b) => {
  return a - b;
};
  • main.vue
typescript 复制代码
  import { add, sub } from "./index.js";
  const n1 = add(1, 2);// ✅ 正常
  const n2 = sub(3); // ❌ 报错:应有 2 个参数,但获得 1 个

3.5 .ts 文件和 .d.ts 文件的区别

TS 中有两种文件类型:

文件类型 特点 用途
.ts 文件 既可以包含类型信息也可以写逻辑代码,可以被编译为 JS 文件 编写程序代码
.d.ts 文件 只能包含类型信息不可以写逻辑代码,不会被编译为 JS 文件,仅做类型校验检查 为 JS 提供类型声明

小结

知识点 示例 说明
defineProps 泛型 defineProps<Props>() 声明 props 类型
withDefaults withDefaults(defineProps<Props>(), {...}) 带默认值的 props
defineEmits 泛型 defineEmits<Emits>() 声明事件类型
.d.ts 共享类型 export interface User {} 跨文件共享类型
DefinitelyTyped @types/jquery 为 JS 库提供类型声明
内置类型声明 lib.es5.d.tslib.dom.d.ts 原生 JS 和浏览器 API

系列回顾

  1. 为什么学 TypeScript?------ 优势、场景与环境搭建
  2. TypeScript 核心语法入门 ------ 类型注解与类型组合
  3. TypeScript 核心语法进阶 ------ 函数与对象类型
  4. TypeScript 核心语法进阶 ------ 字面量类型与类型推论
  5. TypeScript 高级特性 ------ 类型断言与泛型
  6. TypeScript 核心语法应用 ------ Vue 3 中的使用(上)
  7. TypeScript 核心语法应用 ------ Vue 3 中的使用(下)

到这里,整个 TS 系列就全部结束了。从为什么要学 TS,到基础语法、高级特性,再到 Vue 3 中的具体写法,希望能帮你从"会写 TypeScript"走到"能在项目里用好 TypeScript"。

欢迎持续关注 🚀

参考资料

相关推荐
zhangminghuan1 小时前
微信小程序开发核心知识点全景解析(前端必备硬核基础)
前端·微信小程序·小程序
boooooooom1 小时前
尝尝咸淡:从朴素 RAG 到 Graph RAG——一个烹饪问答系统的三层检索升级之路
前端·后端·llm
cindershade1 小时前
让每条前端异常都能回答:该由谁修、为什么现在修
前端
bitbrowser2 小时前
Telegram提示尝试次数过多,换网络和重装有用吗
前端
研☆香2 小时前
前端简单的的变量声明
前端
赵大仁2 小时前
Next.js AI Route Handler 工程化:超时、流式与鉴权
前端·ai·鉴权·next.js·工程化
东风破_3 小时前
(拼多多考题)TypeScript 工具类型:Pick、Omit、Partial、Required
typescript
yuhaiqiang3 小时前
从这两件事就能看出 vibecoding 距离专业作品差距有多大?AI 能抹平技术,但抹不平品味 !
前端·后端·程序员
一次旅行3 小时前
DeepSeek‑V4‑Flash‑Vision‑Exp 小白入门实战|3种传图方式、完整可跑代码、避坑排障
java·前端·人工智能