刚开始用TypeScript的时候,我以为就是给变量加个类型注解,跟Java写泛型差不多。结果一接手中型项目,直接被类型报错搞到怀疑人生。后来发现,真正让TS强大的不是基础类型,而是那些看起来唬人的高级类型。
先看一个最常见的场景:处理API返回的数据格式。
1. 条件类型:让类型自动适配
接手的项目里有个痛点:不同接口返回的数据结构不一样,有的带分页,有的不带。之前同事写了一大堆重复的接口定义,看着就头疼。
为啥要用条件类型?因为我们需要根据输入类型动态决定输出类型。看这个例子:
“typescript
// 定义两种响应格式
type SuccessResponse
code: 200;
data: T;
message: string;
};
type ErrorResponse = {
code: 400 | 500;
data: null;
message: string;
};
// 条件类型:根据code自动推断
type ApiResponse
? SuccessResponse
: ErrorResponse;
// 使用
type UserData = { id: number; name: string };
type UserResponse = ApiResponse
// 结果:{ code: 200; data: UserData; message: string }
`
(这里应该放一张条件类型逻辑的流程图,展示输入条件如何决定输出类型)
刚开始我也觉得条件类型就是if-else的语法糖,直到用它重构了项目里300多行的类型定义,从3.2秒的编译时间降到0.8秒,才意识到它的威力。
2. 映射类型:批量改造类型
另一个坑是:项目里有十几个实体类型,每个都要配合表单校验规则。写重复代码写到手软。
映射类型就是用来解决这个问题的。它允许你基于一个已有类型,创建出一个新类型。官方文档这段文档不够清晰,我直接上代码:
`typescript
// 基础用户类型
interface User {
id: number;
name: string;
email: string;
age: number;
}
// 映射类型:给每个字段加上校验规则
type ValidationRules
[K in ke
yof T]: {
value: T[K];
required: boolean;
validator?: (val: T[K]) => boolean;
errorMessage?: string;
};
};
// 使用
type UserValidation = ValidationRules
// 结果:
// {
// id: { value: number; required: boolean; ... };
// name: { value: string; required: boolean; ... };
// ...
// }
// 实际应用
const userForm: UserValidation = {
id: { value: 1, required: true },
name: { value: '张三', required: true, validator: (v) => v.length > 0 },
email: { value: 'test@example.com', required: true },
age: { value: 25, required: false }
};
`
这个设计真的反人类?一开始我也觉得复杂。但用习惯后,发现它避免了80%的重复类型定义。而且配合编辑器自动补全,写代码速度反而快了。
3. 模板字面量类型:字符串也能有类型
这个功能是TS 4.1引入的,我当时看到公告还觉得:"字符串类型?这不就是枚举吗?" 直到我遇到一个需求:后端返回的事件名称格式是"user:create"、"order:update",要在前端做类型安全的匹配。
模板字面量类型就是干这个的:
`typescript
// 定义事件主体和动作
type Entity = 'user' | 'order' | 'product';
type Action = 'create' | 'update' | 'delete';
// 模板字面量组合
type EventName = ${Entity}:${Action};
// 自动生成所有组合:'user:create' | 'user:update' | ... | 'product:delete'
// 事件处理函数
function handleEvent(event: EventName, data: unknown): void {
const [entity, action] = event.split(':') as [Entity, Action];
console.log(处理${entity}的${action}操作);
}
// 类型安全
handleEvent('user:create', { name: '张三' }); // 正确
handleEvent('user:invalid', {}); // 错误:类型不匹配
`
另一个坑:这个语法跟es6的模板字符串太像了,我来回看了三遍文档才确认是类型层面的东西。但一旦理解,它比写一堆联合类型优雅太多了。
(这里应该放一张模板字面量类型在VSCode中自动补全的截图,显示所有可能的组合)
4. infer关键字:类型推断的瑞士军刀
infer是用来在条件类型中提取类型的。听起来抽象,但实际场景很常见:比如你想知道Promise里到底是什么类型。
`typescript
// 提取Promise的泛型类型
type UnwrapPromise
// 使用
type AsyncData = Promise
type Data = UnwrapPromise
// 更复杂的场景:提取函数返回值
type ReturnOf
function fetchUser(): Promise<{ id: number }> {
return Promise.resolve({ id: 1 });
}
type UserData = ReturnOf
`
这个技巧在写工具类型时特别有用。我写了一个从复杂嵌套对象中提取特定路径值类型的工具,就是靠infer实现的。
5. 实用组合:条件 + 映射 + 模板
最后分享一个真实项目中的组合用法。我们需要根据用户角色显示不同的字段,而且字段的校验规则也不同:
`typescript
// 角色定义
type Role = 'admin' | 'user' | 'guest';
// 根据角色返回不同字段
type RoleFields = {
admin: ['id', 'name', 'email', 'permissions'];
user: ['id', 'name', 'email'];
guest: ['name'];
};
// 字段到类型的映射
type FieldTypeMap = {
id: number;
name: string;
email: string;
permissions: string[];
};
// 高级组合:根据角色生成类型
type ProfileForRole
[K in RoleFields[R][number] as ${R}_${K}]: FieldTypeMap[K];
};
// 使用
type AdminProfile = ProfileForRole<'admin'>;
// 结果:
// {
// admin_id: number;
// admin_name: string;
// admin_email: string;
// admin_permissions: string[];
// }
// 实际应用
function createProfile
// 实现省略
return {} as ProfileForRole
}
const adminProfile = createProfile('admin');
// 类型安全:只能访问 admin_id, admin_name 等
`
(这里应该放一张类型推导结果的截图,显示VSCode中鼠标悬停时显示的具体类型)
这个组合玩法让我意识到,TypeScript的类型系统本质上是一门独立的编程语言,只是借用了JavaScript的语法。一旦掌握,你就能写出自我验证的代码库。
总结一下,你可以立刻用的三个点:
替代多个重载批量生成最后说句坑爹的:这些高级类型在TS 4.x以上版本才能完全支持,生产环境记得先升级。另外不要滥用,我见过把简单接口定义写成类型体操的项目,维护起来比普通代码还痛苦。关键是找到平衡点:类型安全是手段,不是目的。