TypeScript高级类型实战指南

前言

TypeScript已经成为现代前端开发的标配,但很多开发者只是把它当作”带类型的JavaScript”来使用,停留在基础类型注解的层面。实际上,TypeScript的类型系统非常强大,可以实现复杂的类型推导和类型转换。本文将带你深入TypeScript的高级类型世界,掌握类型编程的精髓。


一、泛型:类型的参数化

1.1 基础泛型

泛型允许我们在定义函数、类或接口时,不预先指定具体的类型,而是在使用时再指定。

1
2
3
4
5
6
7
8
9
10
11
// 基础泛型函数
function identity<T>(arg: T): T {
return arg;
}

// 使用时指定类型
const result1 = identity<string>("hello");
const result2 = identity<number>(42);

// 类型推导
const result3 = identity("world"); // 自动推导为string

1.2 泛型约束

使用extends关键字约束泛型的范围:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 约束泛型必须有length属性
interface Lengthwise {
length: number;
}

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

logLength("hello"); // ✅ string有length
logLength([1, 2, 3]); // ✅ Array有length
logLength({ length: 10 }); // ✅ 对象有length
// logLength(123); // ❌ number没有length

1.3 泛型工具函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取数组元素类型
function getFirst<T>(arr: T[]): T | undefined {
return arr[0];
}

// 获取对象属性值
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}

const person = { name: "张三", age: 25 };
const name = getProperty(person, "name"); // string
const age = getProperty(person, "age"); // number
// const x = getProperty(person, "xxx"); // ❌ 类型错误

二、条件类型:类型的if-else

2.1 基础条件类型

条件类型的语法:T extends U ? X : Y

1
2
3
4
5
6
7
8
9
10
11
// 判断类型是否为字符串
type IsString<T> = T extends string ? true : false;

type A = IsString<string>; // true
type B = IsString<number>; // false

// 实用示例:提取Promise的返回类型
type UnwrapPromise<T> = T extends Promise<infer U> ? U : T;

type C = UnwrapPromise<Promise<string>>; // string
type D = UnwrapPromise<number>; // number

2.2 分布式条件类型

当条件类型作用于联合类型时,会自动分发:

1
2
3
4
5
6
7
8
9
10
11
type ToArray<T> = T extends any ? T[] : never;

// 联合类型会分布计算
type E = ToArray<string | number>;
// 相当于:ToArray<string> | ToArray<number>
// 结果:string[] | number[]

// 实用示例:过滤null和undefined
type NonNullable<T> = T extends null | undefined ? never : T;

type F = NonNullable<string | null | undefined>; // string

2.3 infer关键字

infer用于在条件类型中推断类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 获取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;

function greet(): string {
return "Hello";
}

type G = ReturnType<typeof greet>; // string

// 获取函数参数类型
type Parameters<T> = T extends (...args: infer P) => any ? P : never;

function add(a: number, b: number): number {
return a + b;
}

type H = Parameters<typeof add>; // [number, number]

// 获取数组元素类型
type ArrayElement<T> = T extends (infer E)[] ? E : never;

type I = ArrayElement<string[]>; // string

三、映射类型:类型的转换

3.1 基础映射类型

映射类型可以将一个类型转换为另一个类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 将所有属性变为可选
type Partial<T> = {
[P in keyof T]?: T[P];
};

interface User {
name: string;
age: number;
}

type PartialUser = Partial<User>;
// { name?: string; age?: number; }

// 将所有属性变为必选
type Required<T> = {
[P in keyof T]-?: T[P]; // -? 移除可选修饰符
};

// 将所有属性变为只读
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};

3.2 键名映射

TypeScript 4.1+ 支持键名映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 添加前缀
type AddPrefix<T, Prefix extends string> = {
[K in keyof T as `${Prefix}${string & K}`]: T[K];
};

type User = {
name: string;
age: number;
};

type PrefixedUser = AddPrefix<User, "user_">;
// {
// user_name: string;
// user_age: number;
// }

// 过滤属性
type FilterByType<T, U> = {
[K in keyof T as T[K] extends U ? K : never]: T[K];
};

type Person = {
name: string;
age: number;
email: string;
};

type StringProps = FilterByType<Person, string>;
// { name: string; email: string; }

3.3 实战:构建类型安全的表单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
// 表单字段配置
type FormField<T> = {
value: T;
error?: string;
touched: boolean;
};

// 将类型转换为表单类型
type FormState<T> = {
[K in keyof T]: FormField<T[K]>;
};

interface RegisterForm {
username: string;
password: string;
age: number;
}

type RegisterFormState = FormState<RegisterForm>;
// {
// username: FormField<string>;
// password: FormField<string>;
// age: FormField<number>;
// }

// 使用示例
const formState: RegisterFormState = {
username: { value: "", touched: false },
password: { value: "", touched: false, error: "密码不能为空" },
age: { value: 0, touched: false },
};

四、模板字面量类型

4.1 基础用法

TypeScript 4.1引入了模板字面量类型:

1
2
3
4
5
6
7
8
9
// 字符串拼接
type World = "world";
type Greeting = `hello ${World}`; // "hello world"

// HTTP方法
type Method = "GET" | "POST";
type Path = "/users" | "/posts";
type ApiEndpoint = `${Method} ${Path}`;
// "GET /users" | "GET /posts" | "POST /users" | "POST /posts"

4.2 实战:CSS in JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 定义CSS属性
type CSSProperty =
| "color"
| "backgroundColor"
| "fontSize"
| "margin"
| "padding";

// 添加单位
type WithPx<T extends string> = `${T}Px`;
type WithEm<T extends string> = `${T}Em`;

type SizeProperty = "fontSize" | "margin" | "padding";

type CSSPropertyWithUnit =
| CSSProperty
| WithPx<SizeProperty>
| WithEm<SizeProperty>;

// 类型安全的样式对象
type StyleObject = {
[K in CSSPropertyWithUnit]?: string | number;
};

const style: StyleObject = {
color: "red",
fontSizePx: 16,
marginEm: 1.5,
};

4.3 字符串操作

TypeScript提供了内置的字符串操作类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 大写
type Uppercase<S extends string> = intrinsic;
type A = Uppercase<"hello">; // "HELLO"

// 小写
type Lowercase<S extends string> = intrinsic;
type B = Lowercase<"HELLO">; // "hello"

// 首字母大写
type Capitalize<S extends string> = intrinsic;
type C = Capitalize<"hello">; // "Hello"

// 首字母小写
type Uncapitalize<S extends string> = intrinsic;
type D = Uncapitalize<"Hello">; // "hello"

// 实战:生成getter/setter
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type Setters<T> = {
[K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};

interface State {
name: string;
age: number;
}

type StateGetters = Getters<State>;
// {
// getName: () => string;
// getAge: () => number;
// }

type StateSetters = Setters<State>;
// {
// setName: (value: string) => void;
// setAge: (value: number) => void;
// }

五、高级类型实战案例

5.1 类型安全的Redux

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// Action类型
type Action<T extends string, P = void> = P extends void
? { type: T }
: { type: T; payload: P };

// 创建Action
function createAction<T extends string, P = void>(type: T) {
return (payload: P) => ({ type, payload }) as Action<T, P>;
}

// 使用
const increment = createAction<"INCREMENT", number>("INCREMENT");
const decrement = createAction<"DECREMENT">("DECREMENT");

type Actions =
| ReturnType<typeof increment>
| ReturnType<typeof decrement>;

// Reducer
function reducer(state: number = 0, action: Actions): number {
switch (action.type) {
case "INCREMENT":
return state + action.payload; // payload类型安全
case "DECREMENT":
return state - 1;
default:
return state;
}
}

5.2 类型安全的路由

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
// 路由配置
type RouteConfig = {
"/": {};
"/users": {};
"/users/:id": { id: string };
"/posts/:postId/comments/:commentId": {
postId: string;
commentId: string;
};
};

// 提取路径参数
type ExtractParams<T extends string> =
T extends `${infer _Start}:${infer Param}/${infer Rest}`
? { [K in Param | keyof ExtractParams<Rest>]: string }
: T extends `${infer _Start}:${infer Param}`
? { [K in Param]: string }
: {};

// 类型安全的导航函数
function navigate<K extends keyof RouteConfig>(
path: K,
...params: RouteConfig[K] extends {}
? [RouteConfig[K]]
: []
): void {
console.log("Navigate to:", path, params[0]);
}

// 使用
navigate("/"); // ✅
navigate("/users"); // ✅
navigate("/users/:id", { id: "123" }); // ✅
navigate("/posts/:postId/comments/:commentId", {
postId: "1",
commentId: "2"
}); // ✅
// navigate("/users/:id"); // ❌ 缺少参数
// navigate("/users/:id", { id: 123 }); // ❌ 类型错误

5.3 深度只读类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object
? T[K] extends Function
? T[K]
: DeepReadonly<T[K]>
: T[K];
};

interface Config {
database: {
host: string;
port: number;
credentials: {
username: string;
password: string;
};
};
}

type ReadonlyConfig = DeepReadonly<Config>;

const config: ReadonlyConfig = {
database: {
host: "localhost",
port: 5432,
credentials: {
username: "admin",
password: "secret",
},
},
};

// config.database.host = "newhost"; // ❌
// config.database.credentials.password = "newpass"; // ❌

六、类型体操技巧

6.1 元组操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 获取元组第一个元素
type First<T extends any[]> = T extends [infer F, ...any[]] ? F : never;

type A = First<[1, 2, 3]>; // 1

// 获取元组最后一个元素
type Last<T extends any[]> = T extends [...any[], infer L] ? L : never;

type B = Last<[1, 2, 3]>; // 3

// 移除第一个元素
type Shift<T extends any[]> = T extends [any, ...infer Rest] ? Rest : [];

type C = Shift<[1, 2, 3]>; // [2, 3]

// 反转元组
type Reverse<T extends any[]> = T extends [infer F, ...infer Rest]
? [...Reverse<Rest>, F]
: [];

type D = Reverse<[1, 2, 3]>; // [3, 2, 1]

6.2 联合类型操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 联合转交叉
type UnionToIntersection<U> = (
U extends any ? (x: U) => void : never
) extends (x: infer I) => void
? I
: never;

type E = UnionToIntersection<{ a: string } | { b: number }>;
// { a: string } & { b: number }

// 排除属性
type Exclude<T, U> = T extends U ? never : T;

type F = Exclude<"a" | "b" | "c", "a">; // "b" | "c"

// 提取属性
type Extract<T, U> = T extends U ? T : never;

type G = Extract<"a" | "b" | "c", "a" | "b">; // "a" | "b"

七、性能与最佳实践

7.1 避免过度复杂的类型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// ❌ 不好:类型过于复杂
type Complex<T> = T extends { a: infer A }
? A extends { b: infer B }
? B extends { c: infer C }
? C
: never
: never
: never;

// ✅ 好:拆分为多个简单类型
type GetA<T> = T extends { a: infer A } ? A : never;
type GetB<T> = T extends { b: infer B } ? B : never;
type GetC<T> = T extends { c: infer C } ? C : never;

type Better<T> = GetC<GetB<GetA<T>>>;

7.2 使用类型断言时要谨慎

1
2
3
4
5
6
7
8
9
10
11
12
// ❌ 危险:绕过类型检查
const user = { name: "张三" } as any;
user.age.toString(); // 运行时错误

// ✅ 安全:使用类型守卫
function isUser(obj: any): obj is User {
return typeof obj.name === "string" && typeof obj.age === "number";
}

if (isUser(user)) {
user.age.toString(); // 类型安全
}

7.3 合理使用工具类型

TypeScript内置了很多实用的工具类型:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// Partial - 所有属性可选
type Partial<T> = { [P in keyof T]?: T[P] };

// Required - 所有属性必选
type Required<T> = { [P in keyof T]-?: T[P] };

// Readonly - 所有属性只读
type Readonly<T> = { readonly [P in keyof T]: T[P] };

// Pick - 选择部分属性
type Pick<T, K extends keyof T> = { [P in K]: T[P] };

// Omit - 排除部分属性
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

// Record - 创建对象类型
type Record<K extends keyof any, T> = { [P in K]: T };

八、总结

TypeScript的高级类型系统为我们提供了强大的类型编程能力,掌握这些技巧可以:

  1. 提升代码质量:编译期捕获更多错误
  2. 改善开发体验:更好的IDE提示和自动补全
  3. 增强代码可维护性:类型即文档
  4. 实现类型安全:避免运行时类型错误

学习建议

  1. 从基础泛型开始,逐步深入
  2. 多看优秀库的类型定义(如React、Vue)
  3. 善用TypeScript Playground调试类型
  4. 关注TypeScript版本更新,学习新特性

掌握TypeScript高级类型,让你的代码更加健壮和优雅!


参考资料