第四章 TypeScript在Vue中的实践:类型定义与接口、泛型组件、类型安全的props与emit、tsconfig配置优化
好,咱们进入第四章。说实话,这一章是我个人觉得最「值钱」的一章。为什么?因为TypeScript和Vue的结合,说白了就是给项目上了一道保险。我见过太多项目,前期爽快,后期改个需求能改出三四个bug,根源就是类型混乱。
咱们今天就把这块硬骨头啃下来。我会从最基础的类型定义讲起,一直聊到tsconfig的优化配置。你跟着我走一遍,保证以后写Vue项目心里有底。
4.1 类型定义与接口:给数据穿上铠甲
先问个问题:你写Vue组件时,有没有遇到过这种情况——一个prop传进来,你以为是字符串,结果是个数字,然后页面就崩了?
嗯,我遇到过。刚用Vue那会儿,我写了个用户信息组件,userId传了个数字进去,结果模板里直接拼接字符串,出来个NaN。从那以后,我就养成了给所有数据定义类型的习惯。
4.1.1 基础类型定义
在Vue 3 + TypeScript的项目里,我们通常这样定义组件的数据类型:
// 定义一个用户接口
interface IUser {
id: number;
name: string;
email: string;
age?: number; // 可选属性
readonly createdAt: Date; // 只读属性
}
// 在组件中使用
const user = ref<IUser>({
id: 1,
name: '张三',
email: 'zhangsan@example.com',
createdAt: new Date()
})
这里有个小技巧:age?表示这个属性可有可无。我在项目中经常用这个来处理那些「不一定有」的数据,比如用户的头像地址、手机号等。
4.1.2 接口的继承与合并
实际项目中,数据类型往往不是一层就能搞定的。比如用户信息可能包含地址信息:
interface IAddress {
province: string;
city: string;
district: string;
detail: string;
}
interface IUserWithAddress extends IUser {
address: IAddress;
tags: string[];
}
你看,用extends关键字,我们就能把基础的用户信息和地址信息组合起来。这样做的好处是——代码复用,而且修改基础接口时,所有继承它的接口都会自动更新。
I前缀,类型别名加T前缀。这样一眼就能看出是接口还是类型,团队协作时特别有用。
4.2 泛型组件:写一次,用遍所有
泛型组件,说白了就是「万能组件」。你想想看,一个列表组件,今天要展示用户列表,明天要展示商品列表,后天要展示订单列表——难道要写三个组件?
当然不。泛型组件就是来解决这个问题的。
4.2.1 基础泛型组件
<script setup lang="ts">
// 定义一个泛型组件
interface ListProps<T> {
items: T[];
renderItem: (item: T) => string;
}
const props = defineProps<ListProps<any>>();
</script>
<template>
<ul>
<li v-for="(item, index) in props.items" :key="index">
{{ props.renderItem(item) }}
</li>
</ul>
</template>
嗯,这里要注意。上面的写法虽然能用,但类型不够精确。更好的做法是:
<script setup lang="ts" generics="T">
interface ListProps {
items: T[];
renderItem: (item: T) => string;
}
const props = defineProps<ListProps>();
</script>
Vue 3.3+ 支持了generics属性,这样我们就能在defineProps里直接使用泛型参数了。我在项目中用这个写过表格组件、下拉选择组件,效果非常好。
items数组,TypeScript可能推导不出具体的泛型类型。这时候可以显式指定类型:<ListComponent<IUser> :items="userList" />。
4.3 类型安全的props与emit:让父子通信不再出bug
父子组件通信,是Vue开发中最常见的场景。但也是最容易出bug的地方——传错类型、少传参数、事件名拼错……这些我都经历过。
4.3.1 类型安全的props
<script setup lang="ts">
interface Props {
title: string;
count: number;
isActive?: boolean;
items?: string[];
}
const props = withDefaults(defineProps<Props>(), {
isActive: false,
items: () => []
});
</script>
这里有个细节:withDefaults可以给可选属性设置默认值。注意items是数组,必须用函数返回,否则所有组件实例会共享同一个数组引用——这个坑我踩过两次。
4.3.2 类型安全的emit
<script setup lang="ts">
interface Emits {
(e: 'update:title', value: string): void;
(e: 'delete', id: number): void;
(e: 'submit', data: { name: string; age: number }): void;
}
const emit = defineEmits<Emits>();
// 使用
emit('update:title', '新标题');
emit('delete', 123);
emit('submit', { name: '李四', age: 25 });
</script>
你看,每个事件都有明确的参数类型。如果你写错了类型,TypeScript会直接报错。这比运行时才发现问题强太多了。
emit('update:modelValue', ...)实现v-model,结果事件名拼成了update:modelvalue(小写v),调试了半天才发现。有了类型安全的emit,这种低级错误就不会发生了。
4.4 tsconfig配置优化:让TypeScript更听话
最后聊聊tsconfig。说实话,很多开发者对tsconfig的态度是「能用就行」。但我觉得,好的配置能让开发体验提升一大截。
4.4.1 基础配置
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"exclude": ["node_modules", "dist"]
}
这里我重点说几个配置:
| 配置项 | 作用 | 我的建议 |
|---|---|---|
strict: true |
开启所有严格检查 | 必须开启,否则TypeScript的优势少一半 |
paths |
路径别名 | 配合vite的resolve.alias使用,告别../../../ |
skipLibCheck: true |
跳过声明文件检查 | 建议开启,能大幅提升编译速度 |
isolatedModules: true |
确保每个文件可独立编译 | Vite项目必须开启 |
4.4.2 进阶优化
除了基础配置,我还会加一些「锦上添花」的选项:
{
"compilerOptions": {
// ... 基础配置
"noUnusedLocals": true, // 未使用的局部变量报错
"noUnusedParameters": true, // 未使用的参数报错
"noFallthroughCasesInSwitch": true, // switch语句必须包含break
"exactOptionalPropertyTypes": true, // 可选属性不能赋值为undefined
"forceConsistentCasingInFileNames": true // 文件名大小写一致
}
}
这些配置看起来严格,但能帮你提前发现很多潜在问题。我记得有一次,noUnusedLocals帮我发现了一个没用的变量——那个变量本来应该用来更新状态的,结果我忘了写逻辑。要是没这个配置,那个bug可能要到测试阶段才能发现。
noUnusedLocals和noUnusedParameters,等代码清理得差不多了再开其他的。
总结
好了,这一章的内容就到这里。咱们聊了类型定义与接口、泛型组件、类型安全的props与emit,还有tsconfig的优化配置。说白了,TypeScript在Vue中的实践,核心就是「让错误在编译时暴露,而不是在运行时崩溃」。
下一章,我们会聊聊Vue 3的组合式API实战。到时候我会分享一些我在大型项目中总结出来的「组合式API最佳实践」。嗯,保证干货满满。
公众号:蓝海资料掘金营,微信deep3321