TypeScript 高级类型技巧:写出更安全的代码
深入探讨 TypeScript 条件类型、映射类型、类型守卫、泛型约束等高级技巧,通过实战案例展示如何编写更安全、更可维护的代码。
折
折腾侠
2026/03/16 发布
10约 5 分钟772 字 / 574 词00
TypeScript 高级类型技巧:写出更安全的代码
引言
在 TypeScript 开发中,很多人只使用了最基础的类型注解,却错过了 TypeScript 真正强大的功能。本文将深入探讨 5 个高级类型技巧,帮助你写出更安全、更可维护的代码。
一、条件类型:让类型智能起来
条件类型允许我们根据类型参数来决定最终类型,类似于 JavaScript 中的三元表达式。
TypeScript
// 基础语法
type IsString<T> = T extends string ? true : false;
type A = IsString<string>; // true
type B = IsString<number>; // false
实际应用场景:提取 Promise 返回类型
TypeScript
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type Result = UnwrapPromise<Promise<string>>; // string
type Result2 = UnwrapPromise<number>; // number
这个技巧在处理异步函数返回值时特别有用,可以避免手动追踪嵌套的 Promise 类型。
二、映射类型:批量转换属性
映射类型允许我们基于现有类型创建新类型,类似于数组的 INLINE_CODE_0 方法。
TypeScript
// 将所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};
// 将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
实战:创建 API 响应类型
TypeScript
interface User {
id: number;
name: string;
email: string;
createdAt: Date;
}
// API 返回的 User 需要序列化日期
type SerializedUser = {
[P in keyof User]: User[P] extends Date ? string : User[P];
};
// 结果:id, name, email 保持原类型,createdAt 变为 string
三、工具类型的组合使用
TypeScript 内置了多个实用工具类型,组合使用可以解决复杂场景。
TypeScript
interface Config {
apiUrl: string;
timeout: number;
retry: number;
debug: boolean;
}
// 只保留部分字段
type ApiConfig = Pick<Config, 'apiUrl' | 'timeout'>;
// 排除某些字段
type RuntimeConfig = Omit<Config, 'apiUrl'>;
// 创建更新类型(部分字段可选)
type ConfigUpdate = Partial<Pick<Config, 'timeout' | 'debug'>>;
深度 Partial:处理嵌套对象
内置的 INLINE_CODE_1 只处理第一层,对于嵌套对象需要自定义:
TypeScript
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
interface NestedConfig {
database: {
host: string;
port: number;
};
cache: {
enabled: boolean;
ttl: number;
};
}
// 现在可以只更新深层的某个属性
type ConfigUpdate = DeepPartial<NestedConfig>;
四、类型守卫:运行时类型检查
类型守卫函数可以在运行时缩窄类型,让 TypeScript 在后续代码中知道具体类型。
TypeScript
// 自定义类型守卫
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function processInput(input: string | number) {
if (isString(input)) {
// 这里 input 被缩窄为 string
return input.toUpperCase();
}
// 这里 input 被缩窄为 number
return input.toFixed(2);
}
in 操作符守卫
TypeScript
interface Dog {
bark(): void;
}
interface Cat {
meow(): void;
}
function makeSound(animal: Dog | Cat) {
if ('bark' in animal) {
animal.bark(); // animal 是 Dog
} else {
animal.meow(); // animal 是 Cat
}
}
五、泛型约束:限制类型参数范围
使用 INLINE_CODE_2 关键字可以约束泛型参数必须满足某些条件。
TypeScript
// 约束 T 必须有 length 属性
function logLength<T extends { length: number }>(value: T): void {
console.log(value.length);
}
logLength('hello'); // ✓ 5
logLength([1, 2, 3]); // ✓ 3
logLength(123); // ✗ number 没有 length 属性
多约束场景
TypeScript
interface Identifiable {
id: string | number;
}
interface Creatable {
createdAt: Date;
}
// 同时约束多个接口
function processEntity<T extends Identifiable & Creatable>(entity: T) {
console.log(`ID: ${entity.id}, Created: ${entity.createdAt}`);
}
六、实战:构建类型安全的 API 客户端
结合以上技巧,我们来构建一个类型安全的 API 客户端。
TypeScript
// 定义 API 响应结构
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
// 提取数据类型
type ResponseData<T> = T extends ApiResponse<infer U> ? U : never;
// 定义用户类型
interface User {
id: number;
name: string;
email: string;
}
// API 函数返回类型
async function fetchUser(id: number): Promise<ApiResponse<User>> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
// 使用
async function getUser(id: number) {
const result = await fetchUser(id);
// TypeScript 知道 result.data 是 User 类型
console.log(result.data.name); // ✓ 类型安全
}
七、常见陷阱与解决方案
陷阱 1:any 类型滥用
TypeScript
// ❌ 错误:失去类型检查
function process(data: any) {
return data.value;
}
// ✓ 正确:使用 unknown
function processSafe(data: unknown) {
if (typeof data === 'object' && data !== null && 'value' in data) {
return (data as { value: string }).value;
}
throw new Error('Invalid data');
}
陷阱 2:过度使用类型断言
TypeScript
// ❌ 错误:绕过类型检查
const element = document.getElementById('app') as HTMLDivElement;
// ✓ 正确:先检查
const element = document.getElementById('app');
if (element instanceof HTMLDivElement) {
// 安全使用 element
}
总结
掌握这些高级类型技巧后,你可以:
- 编写更安全的代码:在编译时捕获更多错误
- 提高开发效率:获得更好的自动补全和类型提示
- 增强代码可维护性:类型即文档,新人更容易理解
- 减少运行时错误:类型检查覆盖更多边界情况
TypeScript 的类型系统非常强大,但需要时间和实践来掌握。建议从实际项目中的小场景开始应用,逐步深入。
延伸学习
- TypeScript 官方 Handbook:https://www.typescriptlang.org/docs/
- TypeScript Deep Dive:https://basarat.gitbook.io/typescript/
- 实战项目:尝试用高级类型重构现有代码库
本文首发于 railx 博客,欢迎转载但请注明出处。