如果你已经能在项目中使用基本的 TypeScript 类型标注,那么恭喜你——你已经解决了 70% 的类型安全问题。但剩下的 30% 才是 TypeScript 真正强大的地方:通过类型编程在编译期就消除潜在的运行时错误。

泛型约束:让泛型不再”泛”

最简单的泛型可以接受任何类型,但你无法安全地访问参数的任何属性。泛型约束就是为了解决这个问题:

1
2
3
4
5
6
7
8
9
10
interface HasLength { length: number; }

function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length);
return arg;
}

logLength("hello"); // ✅ 5
logLength([1, 2, 3]); // ✅ 3
logLength(123); // ❌ 编译错误:number 没有 length 属性

实战:约束 Repository 模式

1
2
3
4
5
6
7
interface Repository<T extends { id: string }> {
findById(id: string): T | undefined;
save(entity: T): void;
}

interface User { id: string; name: string; email: string; }
const userRepo: Repository<User> = { /* ... */ };

条件类型:类型的 if/else

条件类型让类型能根据条件分支——就像写代码一样写类型:

1
2
3
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<42>; // false

infer 关键字是条件类型的灵魂——它让 TypeScript 在类型推断过程中”捕获”某个类型变量:

1
2
3
4
5
6
// 提取 Promise 的返回值类型
type Unwrap<T> = T extends Promise<infer U> ? Unwrap<U> : T;
type Result = Unwrap<Promise<Promise<string>>>; // → string

// 提取函数的返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

模板字面量类型:字符串的类型级操作

TypeScript 4.1 引入了模板字面量类型——你可以像模板字符串一样操作类型:

1
2
3
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type FocusEvent = EventName<"focus">; // "onFocus"

生产级应用:类型安全的事件系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Events = {
login: { userId: string; timestamp: number };
logout: { userId: string };
purchase: { itemId: string; amount: number };
};

type EventHandler<K extends keyof Events> = (data: Events[K]) => void;

function on<K extends keyof Events>(event: K, handler: EventHandler<K>) {
/* 类型安全的事件注册 */
}

on("login", (data) => {
console.log(data.userId); // ✅ 有智能提示
// console.log(data.amount); // ❌ 编译错误
});

映射类型与工具类型

TypeScript 内置了大量实用工具类型(Utility Types),它们本质上都是映射类型的应用:

工具类型 作用 示例
Partial<T> 所有属性变为可选 Partial<User>
Required<T> 所有属性变为必填 Required<Partial<User>>
Pick<T, K> 选取指定属性 `Pick<User, ‘name’
Omit<T, K> 排除指定属性 Omit<User, 'id'>
Record<K, V> 构造键值对类型 Record<string, User>

总结

TypeScript 的类型系统远不止 stringnumber 这些基础标注。泛型约束让你在保持灵活性的同时不丢失类型安全,条件类型让你能写出”自适应”的类型逻辑,模板字面量类型则打开了字符串类型操作的新世界。把它们组合起来,你能在编译期就消除一整个类别的运行时 Bug——这才是 TypeScript 的核心价值所在。