🆚 与 Java 泛型的区别:Java 泛型有类型擦除,运行时泛型信息丢失。TS 的类型系统完全在编译时,但功能远比 Java 泛型强大(条件类型、映射类型等在 Java 中无对应物)。
1. 泛型函数
泛型允许你编写可复用的、类型安全的组件。类型参数用尖括号声明,调用时可显式指定或由编译器自动推断:
// 基本泛型函数
function identity<T>(arg: T): T {
return arg;
}
// 显式指定类型参数
const str = identity<string>("hello");
// 类型推断——编译器自动推导 T = number
const num = identity(42);
// 多个类型参数
function pair<A, B>(first: A, second: B): [A, B] {
return [first, second];
}
const p = pair("age", 30); // 推断为 [string, number]
// 泛型箭头函数
const toArray = <T,>(value: T): T[] => [value];
注意箭头函数中 <T,> 的逗号——在 .tsx 文件中需要这个逗号来避免与 JSX 标签混淆。
2. 泛型接口与类
// 泛型接口
interface Container<T> {
value: T;
getValue(): T;
}
const numBox: Container<number> = {
value: 42,
getValue() { return this.value; }
};
// 泛型类——实现一个类型安全的栈
class Stack<T> {
private items: T[] = [];
push(item: T): void { this.items.push(item); }
pop(): T | undefined { return this.items.pop(); }
peek(): T | undefined { return this.items[this.items.length - 1]; }
get size(): number { return this.items.length; }
}
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
// numberStack.push("3"); // ✗ 编译错误
3. 泛型约束
使用 extends 关键字约束类型参数,确保泛型满足某些条件:
interface HasLength {
length: number;
}
// T 必须有 length 属性
function logLength<T extends HasLength>(arg: T): number {
console.log(arg.length);
return arg.length;
}
logLength("hello"); // ✓ string 有 length
logLength([1, 2, 3]); // ✓ 数组有 length
// logLength(123); // ✗ number 没有 length
// keyof 约束——限制 key 必须是 obj 的属性名
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: "Alice", age: 30 };
getProperty(user, "name"); // ✓ 返回 string
// getProperty(user, "email"); // ✗ "email" 不在 keyof typeof user 中
4. 常用 Utility Types
TypeScript 内置了大量实用工具类型,掌握它们可以大幅减少样板代码:
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial<T> —— 所有属性变为可选
type UpdateUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number }
// Required<T> —— 所有属性变为必填
type StrictUser = Required<Partial<User>>;
// Pick<T, K> —— 选取部分属性
type UserSummary = Pick<User, "id" | "name">;
// { id: number; name: string }
// Omit<T, K> —— 排除部分属性
type UserWithoutEmail = Omit<User, "email">;
// Record<K, V> —— 构造键值对类型
type RolePermissions = Record<"admin" | "user" | "guest", string[]>;
// Readonly<T> —— 所有属性变为只读
type FrozenUser = Readonly<User>;
// ReturnType<T> / Parameters<T>
function createUser(name: string, age: number) { return { name, age }; }
type Created = ReturnType<typeof createUser>; // { name: string; age: number }
type Args = Parameters<typeof createUser>; // [string, number]
5. 条件类型
条件类型让你在类型层面做 if/else 判断,语法类似三元表达式:
// 基本语法:T extends U ? X : Y
type IsString<T> = T extends string ? "yes" : "no";
type A = IsString<string>; // "yes"
type B = IsString<number>; // "no"
// 内置条件类型的实现原理
type MyNonNullable<T> = T extends null | undefined ? never : T;
type C = MyNonNullable<string | null>; // string
type MyExtract<T, U> = T extends U ? T : never;
type D = MyExtract<"a" | "b" | "c", "a" | "c">; // "a" | "c"
type MyExclude<T, U> = T extends U ? never : T;
type E = MyExclude<"a" | "b" | "c", "a">; // "b" | "c"
联合类型在条件类型中会自动分发——T extends U ? X : Y 会对联合中的每个成员分别求值再合并结果。
6. 映射类型
// 映射类型:遍历已有类型的键来创建新类型
type MyReadonly<T> = {
readonly [K in keyof T]: T[K];
};
type MyPartial<T> = {
[K in keyof T]?: T[K];
};
// 自定义映射类型——将所有属性值变为 Promise
type Async<T> = {
[K in keyof T]: Promise<T[K]>;
};
interface SyncAPI {
getUser(): User;
getAge(): number;
}
type AsyncAPI = Async<SyncAPI>;
// { getUser(): Promise<User>; getAge(): Promise<number> }
// 键的重映射(as 子句)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
type UserGetters = Getters<User>;
// { getId: () => number; getName: () => string; ... }
7. 模板字面量类型
// 模板字面量类型——在类型层面拼接字符串
type EventName = `on${string}`;
const e1: EventName = "onClick"; // ✓
// const e2: EventName = "click"; // ✗ 缺少 "on" 前缀
// 结合联合类型,生成所有组合
type Color = "red" | "blue";
type Size = "small" | "large";
type Style = `${Color}-${Size}`;
// "red-small" | "red-large" | "blue-small" | "blue-large"
// 内置字符串操作类型
type Upper = Uppercase<"hello">; // "HELLO"
type Lower = Lowercase<"HELLO">; // "hello"
type Cap = Capitalize<"hello">; // "Hello"
type Uncap = Uncapitalize<"Hello">; // "hello"
8. infer 关键字
infer 在条件类型中声明一个待推断的类型变量,让你从复杂类型中提取子类型:
// 提取函数返回类型(ReturnType 的实现原理)
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type R1 = MyReturnType<() => string>; // string
type R2 = MyReturnType<(x: number) => void>; // void
// 提取函数参数类型
type MyParameters<T> = T extends (...args: infer P) => any ? P : never;
type P1 = MyParameters<(a: string, b: number) => void>; // [string, number]
// 提取 Promise 包裹的类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;
type V1 = UnwrapPromise<Promise<string>>; // string
type V2 = UnwrapPromise<number>; // number
// 提取数组元素类型
type ElementOf<T> = T extends (infer E)[] ? E : never;
type El = ElementOf<string[]>; // string
📝 本章要点
- ✦泛型是编写可复用、类型安全代码的基础,函数、接口、类都能使用泛型
- ✦用
extends约束泛型参数,用keyof获取对象键的联合类型 - ✦熟练使用内置 Utility Types(Partial、Pick、Omit、Record 等)能极大提升开发效率
- ✦条件类型 +
infer是高级类型编程的核心,能从已有类型中提取和变换信息 - ✦映射类型和模板字面量类型让你能批量生成和变换类型,实现强大的类型抽象