阅读本文你能学到什么?
- 如何为组件的
props标注类型- 如何为组件的
emits标注类型- 类型声明文件(
.d.ts)的用途与用法
前言
上一篇我们学会了 Vue 3 组件内部状态和逻辑的 TS 用法------ref、reactive、computed、事件处理、模板引用等。但一个组件很少是独立存在的,组件之间总要传数据。
这一篇,就来看看在 <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包及其版本信息。
- 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.ts,index.js和main.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.ts、lib.dom.d.ts |
原生 JS 和浏览器 API |
系列回顾
- 为什么学 TypeScript?------ 优势、场景与环境搭建
- TypeScript 核心语法入门 ------ 类型注解与类型组合
- TypeScript 核心语法进阶 ------ 函数与对象类型
- TypeScript 核心语法进阶 ------ 字面量类型与类型推论
- TypeScript 高级特性 ------ 类型断言与泛型
- TypeScript 核心语法应用 ------ Vue 3 中的使用(上)
- TypeScript 核心语法应用 ------ Vue 3 中的使用(下)
到这里,整个 TS 系列就全部结束了。从为什么要学 TS,到基础语法、高级特性,再到 Vue 3 中的具体写法,希望能帮你从"会写 TypeScript"走到"能在项目里用好 TypeScript"。
欢迎持续关注 🚀