TypeScript 高级类型
全面掌握 TypeScript 高级类型系统的指南,包括泛型、条件类型、映射类型、模板字面量类型和工具类型,用于构建健壮、类型安全的应用程序。
何时使用此技能
- 构建类型安全的库或框架
- 创建可复用的泛型组件
- 实现复杂的类型推断逻辑
- 设计类型安全的 API 客户端
- 构建表单验证系统
- 创建强类型配置对象
- 实现类型安全的状态管理
- 将 JavaScript 代码库迁移到 TypeScript
核心概念
1. 泛型
目的: 创建可复用、类型灵活的组件,同时保持类型安全。
基础泛型函数:
typescript
1function identity<T>(value: T): T {
2 return value;
3}
4
5const num = identity<number>(42); // 类型: number
6const str = identity<string>("hello"); // 类型: string
7const auto = identity(true); // 类型推断: boolean
泛型约束:
typescript
1type HasLength = {
2 length: number;
3};
4
5function logLength<T extends HasLength>(item: T): T {
6 console.log(item.length);
7 return item;
8}
9
10logLength("hello"); // OK: string 有 length 属性
11logLength([1, 2, 3]); // OK: 数组有 length 属性
12logLength({ length: 10 }); // OK: 对象有 length 属性
13// logLength(42); // 错误: number 没有 length 属性
多个类型参数:
typescript
1function merge<T, U>(obj1: T, obj2: U): T & U {
2 return { ...obj1, ...obj2 };
3}
4
5const merged = merge({ name: "John" }, { age: 30 });
6// 类型: { name: string } & { age: number }
2. 条件类型
目的: 创建依赖于条件的类型,实现复杂的类型逻辑。
基础条件类型:
typescript
1type IsString<T> = T extends string ? true : false;
2
3type A = IsString<string>; // true
4type B = IsString<number>; // false
提取返回类型:
typescript
1type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
2
3function getUser() {
4 return { id: 1, name: "John" };
5}
6
7type User = ReturnType<typeof getUser>;
8// 类型: { id: number; name: string; }
分布式条件类型:
typescript
1type ToArray<T> = T extends any ? T[] : never;
2
3type StrOrNumArray = ToArray<string | number>;
4// 类型: string[] | number[]
嵌套条件:
typescript
1type TypeName<T> = T extends string
2 ? "string"
3 : T extends number
4 ? "number"
5 : T extends boolean
6 ? "boolean"
7 : T extends undefined
8 ? "undefined"
9 : T extends Function
10 ? "function"
11 : "object";
12
13type T1 = TypeName<string>; // "string"
14type T2 = TypeName<() => void>; // "function"
3. 映射类型
目的: 通过遍历现有类型的属性来转换类型。
基础映射类型:
typescript
1type Readonly<T> = {
2 readonly [P in keyof T]: T[P];
3};
4
5type User = {
6 id: number;
7 name: string;
8};
9
10type ReadonlyUser = Readonly<User>;
11// 类型: { readonly id: number; readonly name: string; }
可选属性:
typescript
1type Partial<T> = {
2 [P in keyof T]?: T[P];
3};
4
5type PartialUser = Partial<User>;
6// 类型: { id?: number; name?: string; }
键重映射:
typescript
1type Getters<T> = {
2 [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
3};
4
5type Person = {
6 name: string;
7 age: number;
8};
9
10type PersonGetters = Getters<Person>;
11// 类型: { getName: () => string; getAge: () => number; }
属性过滤:
typescript
1type PickByType<T, U> = {
2 [K in keyof T as T[K] extends U ? K : never]: T[K];
3};
4
5type Mixed = {
6 id: number;
7 name: string;
8 age: number;
9 active: boolean;
10};
11
12type OnlyNumbers = PickByType<Mixed, number>;
13// 类型: { id: number; age: number; }
4. 模板字面量类型
目的: 创建具有模式匹配和转换功能的字符串类型。
基础模板字面量:
typescript
1type EventName = "click" | "focus" | "blur";
2type EventHandler = `on${Capitalize<EventName>}`;
3// 类型: "onClick" | "onFocus" | "onBlur"
字符串操作:
typescript
1type UppercaseGreeting = Uppercase<"hello">; // "HELLO"
2type LowercaseGreeting = Lowercase<"HELLO">; // "hello"
3type CapitalizedName = Capitalize<"john">; // "John"
4type UncapitalizedName = Uncapitalize<"John">; // "john"
路径构建:
typescript
1type Path<T> = T extends object
2 ? {
3 [K in keyof T]: K extends string ? `${K}` | `${K}.${Path<T[K]>}` : never;
4 }[keyof T]
5 : never;
6
7type Config = {
8 server: {
9 host: string;
10 port: number;
11 };
12 database: {
13 url: string;
14 };
15};
16
17type ConfigPath = Path<Config>;
18// 类型: "server" | "database" | "server.host" | "server.port" | "database.url"
5. 工具类型
内置工具类型:
typescript
1// Partial<T> - 使所有属性可选
2type PartialUser = Partial<User>;
3
4// Required<T> - 使所有属性必需
5type RequiredUser = Required<PartialUser>;
6
7// Readonly<T> - 使所有属性只读
8type ReadonlyUser = Readonly<User>;
9
10// Pick<T, K> - 选择特定属性
11type UserName = Pick<User, "name" | "email">;
12
13// Omit<T, K> - 移除特定属性
14type UserWithoutPassword = Omit<User, "password">;
15
16// Exclude<T, U> - 从联合类型中排除类型
17type T1 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
18
19// Extract<T, U> - 从联合类型中提取类型
20type T2 = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"
21
22// NonNullable<T> - 排除 null 和 undefined
23type T3 = NonNullable<string | null | undefined>; // string
24
25// Record<K, T> - 创建键为 K、值为 T 的对象类型
26type PageInfo = Record<"home" | "about", { title: string }>;
高级模式
模式 1:类型安全的事件发射器
typescript
1type EventMap = {
2 "user:created": { id: string; name: string };
3 "user:updated": { id: string };
4 "user:deleted": { id: string };
5};
6
7class TypedEventEmitter<T extends Record<string, any>> {
8 private listeners: {
9 [K in keyof T]?: Array<(data: T[K]) => void>;
10 } = {};
11
12 on<K extends keyof T>(event: K, callback: (data: T[K]) => void): void {
13 if (!this.listeners[event]) {
14 this.listeners[event] = [];
15 }
16 this.listeners[event]!.push(callback);
17 }
18
19 emit<K extends keyof T>(event: K, data: T[K]): void {
20 const callbacks = this.listeners[event];
21 if (callbacks) {
22 callbacks.forEach((callback) => callback(data));
23 }
24 }
25}
26
27const emitter = new TypedEventEmitter<EventMap>();
28
29emitter.on("user:created", (data) => {
30 console.log(data.id, data.name); // 类型安全!
31});
32
33emitter.emit("user:created", { id: "1", name: "John" });
34// emitter.emit("user:created", { id: "1" }); // 错误: 缺少 'name'
模式 2:类型安全的 API 客户端
typescript
1type HTTPMethod = "GET" | "POST" | "PUT" | "DELETE";
2
3type EndpointConfig = {
4 "/users": {
5 GET: { response: User[] };
6 POST: { body: { name: string; email: string }; response: User };
7 };
8 "/users/:id": {
9 GET: { params: { id: string }; response: User };
10 PUT: { params: { id: string }; body: Partial<User>; response: User };
11 DELETE: { params: { id: string }; response: void };
12 };
13};
14
15type ExtractParams<T> = T extends { params: infer P } ? P : never;
16type ExtractBody<T> = T extends { body: infer B } ? B : never;
17type ExtractResponse<T> = T extends { response: infer R } ? R : never;
18
19class APIClient<Config extends Record<string, Record<HTTPMethod, any>>> {
20 async request<Path extends keyof Config, Method extends keyof Config[Path]>(
21 path: Path,
22 method: Method,
23 ...[options]: ExtractParams<Config[Path][Method]> extends never
24 ? ExtractBody<Config[Path][Method]> extends never
25 ? []
26 : [{ body: ExtractBody<Config[Path][Method]> }]
27 : [
28 {
29 params: ExtractParams<Config[Path][Method]>;
30 body?: ExtractBody<Config[Path][Method]>;
31 },
32 ]
33 ): Promise<ExtractResponse<Config[Path][Method]>> {
34 // 实现代码
35 return {} as any;
36 }
37}
38
39const api = new APIClient<EndpointConfig>();
40
41// 类型安全的 API 调用
42const users = await api.request("/users", "GET");
43// 类型: User[]
44
45const newUser = await api.request("/users", "POST", {
46 body: { name: "John", email: "john@example.com" },
47});
48// 类型: User
49
50const user = await api.request("/users/:id", "GET", {
51 params: { id: "123" },
52});
53// 类型: User
模式 3:类型安全的建造者模式
typescript
1type BuilderState<T> = {
2 [K in keyof T]: T[K] | undefined;
3};
4
5type RequiredKeys<T> = {
6 [K in keyof T]-?: {} extends Pick<T, K> ? never : K;
7}[keyof T];
8
9type OptionalKeys<T> = {
10 [K in keyof T]-?: {} extends Pick<T, K> ? K : never;
11}[keyof T];
12
13type IsComplete<T, S> =
14 RequiredKeys<T> extends keyof S
15 ? S[RequiredKeys<T>] extends undefined
16 ? false
17 : true
18 : false;
19
20class Builder<T, S extends BuilderState<T> = {}> {
21 private state: S = {} as S;
22
23 set<K extends keyof T>(key: K, value: T[K]): Builder<T, S & Record<K, T[K]>> {
24 this.state[key] = value;
25 return this as any;
26 }
27
28 build(this: IsComplete<T, S> extends true ? this : never): T {
29 return this.state as T;
30 }
31}
32
33type User = {
34 id: string;
35 name: string;
36 email: string;
37 age?: number;
38};
39
40const builder = new Builder<User>();
41
42const user = builder
43 .set("id", "1")
44 .set("name", "John")
45 .set("email", "john@example.com")
46 .build(); // OK: 所有必需字段已设置
47
48// const incomplete = builder
49// .set("id", "1")
50// .build(); // 错误: 缺少必需字段
模式 4:深度只读/可选
typescript
1type DeepReadonly<T> = {
2 readonly [P in keyof T]: T[P] extends object
3 ? T[P] extends Function
4 ? T[P]
5 : DeepReadonly<T[P]>
6 : T[P];
7};
8
9type DeepPartial<T> = {
10 [P in keyof T]?: T[P] extends object
11 ? T[P] extends Array<infer U>
12 ? Array<DeepPartial<U>>
13 : DeepPartial<T[P]>
14 : T[P];
15};
16
17type Config = {
18 server: {
19 host: string;
20 port: number;
21 ssl: {
22 enabled: boolean;
23 cert: string;
24 };
25 };
26 database: {
27 url: string;
28 pool: {
29 min: number;
30 max: number;
31 };
32 };
33};
34
35type ReadonlyConfig = DeepReadonly<Config>;
36// 所有嵌套属性都是只读的
37
38type PartialConfig = DeepPartial<Config>;
39// 所有嵌套属性都是可选的
模式 5:类型安全的表单验证
typescript
1type ValidationRule<T> = {
2 validate: (value: T) => boolean;
3 message: string;
4};
5
6type FieldValidation<T> = {
7 [K in keyof T]?: ValidationRule<T[K]>[];
8};
9
10type ValidationErrors<T> = {
11 [K in keyof T]?: string[];
12};
13
14class FormValidator<T extends Record<string, any>> {
15 constructor(private rules: FieldValidation<T>) {}
16
17 validate(data: T): ValidationErrors<T> | null {
18 const errors: ValidationErrors<T> = {};
19 let hasErrors = false;
20
21 for (const key in this.rules) {
22 const fieldRules = this.rules[key];
23 const value = data[key];
24
25 if (fieldRules) {
26 const fieldErrors: string[] = [];
27
28 for (const rule of fieldRules) {
29 if (!rule.validate(value)) {
30 fieldErrors.push(rule.message);
31 }
32 }
33
34 if (fieldErrors.length > 0) {
35 errors[key] = fieldErrors;
36 hasErrors = true;
37 }
38 }
39 }
40
41 return hasErrors ? errors : null;
42 }
43}
44
45type LoginForm = {
46 email: string;
47 password: string;
48};
49
50const validator = new FormValidator<LoginForm>({
51 email: [
52 {
53 validate: (v) => v.includes("@"),
54 message: "邮箱必须包含 @",
55 },
56 {
57 validate: (v) => v.length > 0,
58 message: "邮箱为必填项",
59 },
60 ],
61 password: [
62 {
63 validate: (v) => v.length >= 8,
64 message: "密码至少需要 8 个字符",
65 },
66 ],
67});
68
69const errors = validator.validate({
70 email: "invalid",
71 password: "short",
72});
73// 类型: { email?: string[]; password?: string[]; } | null
模式 6:可辨识联合
typescript
1type Success<T> = {
2 status: "success";
3 data: T;
4};
5
6type Error = {
7 status: "error";
8 error: string;
9};
10
11type Loading = {
12 status: "loading";
13};
14
15type AsyncState<T> = Success<T> | Error | Loading;
16
17function handleState<T>(state: AsyncState<T>): void {
18 switch (state.status) {
19 case "success":
20 console.log(state.data); // 类型: T
21 break;
22 case "error":
23 console.log(state.error); // 类型: string
24 break;
25 case "loading":
26 console.log("加载中...");
27 break;
28 }
29}
30
31// 类型安全的状态机
32type State =
33 | { type: "idle" }
34 | { type: "fetching"; requestId: string }
35 | { type: "success"; data: any }
36 | { type: "error"; error: Error };
37
38type Event =
39 | { type: "FETCH"; requestId: string }
40 | { type: "SUCCESS"; data: any }
41 | { type: "ERROR"; error: Error }
42 | { type: "RESET" };
43
44function reducer(state: State, event: Event): State {
45 switch (state.type) {
46 case "idle":
47 return event.type === "FETCH"
48 ? { type: "fetching", requestId: event.requestId }
49 : state;
50 case "fetching":
51 if (event.type === "SUCCESS") {
52 return { type: "success", data: event.data };
53 }
54 if (event.type === "ERROR") {
55 return { type: "error", error: event.error };
56 }
57 return state;
58 case "success":
59 case "error":
60 return event.type === "RESET" ? { type: "idle" } : state;
61 }
62}
类型推断技术
1. infer 关键字
typescript
1// 提取数组元素类型
2type ElementType<T> = T extends (infer U)[] ? U : never;
3
4type NumArray = number[];
5type Num = ElementType<NumArray>; // number
6
7// 提取 Promise 类型
8type PromiseType<T> = T extends Promise<infer U> ? U : never;
9
10type AsyncNum = PromiseType<Promise<number>>; // number
11
12// 提取函数参数
13type Parameters<T> = T extends (...args: infer P) => any ? P : never;
14
15function foo(a: string, b: number) {}
16type FooParams = Parameters<typeof foo>; // [string, number]
2. 类型守卫
typescript
1function isString(value: unknown): value is string {
2 return typeof value === "string";
3}
4
5function isArrayOf<T>(
6 value: unknown,
7 guard: (item: unknown) => item is T,
8): value is T[] {
9 return Array.isArray(value) && value.every(guard);
10}
11
12const data: unknown = ["a", "b", "c"];
13
14if (isArrayOf(data, isString)) {
15 data.forEach((s) => s.toUpperCase()); // 类型: string[]
16}
3. 断言函数
typescript
1function assertIsString(value: unknown): asserts value is string {
2 if (typeof value !== "string") {
3 throw new Error("不是字符串");
4 }
5}
6
7function processValue(value: unknown) {
8 assertIsString(value);
9 // value 现在被类型化为 string
10 console.log(value.toUpperCase());
11}
最佳实践
- 使用
unknown 而非 any:强制进行类型检查
- 统一使用
type 定义类型:语法一致,支持联合、交叉、映射等所有类型操作
- 善用类型别名:提高代码可读性
- 利用类型推断:让 TypeScript 在可能时自动推断
- 创建辅助类型:构建可复用的类型工具
- 使用 const 断言:保留字面量类型
- 避免类型断言:使用类型守卫代替
- 为复杂类型添加文档:添加 JSDoc 注释
- 使用严格模式:启用所有严格编译器选项
- 测试你的类型:使用类型测试验证类型行为
类型测试
typescript
1// 类型断言测试
2type AssertEqual<T, U> = [T] extends [U]
3 ? [U] extends [T]
4 ? true
5 : false
6 : false;
7
8type Test1 = AssertEqual<string, string>; // true
9type Test2 = AssertEqual<string, number>; // false
10type Test3 = AssertEqual<string | number, string>; // false
11
12// 期望错误辅助类型
13type ExpectError<T extends never> = T;
14
15// 使用示例
16type ShouldError = ExpectError<AssertEqual<string, number>>;
常见陷阱
- 过度使用
any:违背了 TypeScript 的目的
- 忽略严格空值检查:可能导致运行时错误
- 类型过于复杂:可能拖慢编译速度
- 不使用可辨识联合:错失类型收窄的机会
- 忘记 readonly 修饰符:允许意外的修改
- 循环类型引用:可能导致编译器错误
- 未处理边界情况:如空数组或 null 值
性能考虑
- 避免深度嵌套的条件类型
- 尽可能使用简单类型
- 缓存复杂的类型计算
- 限制递归类型的递归深度
- 在生产环境使用构建工具跳过类型检查
资源