泛型
泛型(Generics)是TypeScript类型系统的核心特性之一,它允许我们在定义函数、接口或类时不预先指定具体类型,而是在使用时动态指定。这种"类型参数化"的能力让代码既灵活又类型安全。
基础概念
为什么需要泛型
- 可重用的组件:泛型允许我们创建可重用的组件,如数组、列表、映射等,而不需要为每种类型都编写不同的代码。
- 类型安全:在编译时,泛型会检查参数类型是否符合预期,确保类型安全。
- 代码重用:泛型使代码更简洁,避免了重复编写相似的代码。
- 可维护性:当需要修改组件时,泛型使代码更易维护,因为类型参数化后,组件的实现逻辑可以独立于具体的类型。
在实际开发中,我们经常遇到这样的场景:同一个函数需要处理多种类型的数据。如果没有泛型,我们可能会写出这样的代码:
TypeScript
// 方案1:为每种类型写一个函数(代码重复)
function pushStringArr(arr: string[], item: string): string[] {
arr.push(item);
return arr;
}
function pushNumberArr(arr: number[], item: number): number[] {
arr.push(item);
return arr;
}
// 方案2:使用 any(丢失类型安全)
function pushAnyArr(arr: any[], item: any): any[] {
arr.push(item);
return arr;
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- 方案1违反了DRY(Don't Repeat Yourself)原则
- 方案2则完全放弃了类型检查。
泛型提供了第三种方案:既保持代码复用,又保持类型安全
基本语法
TypeScript
function 函数名<T>(参数: T): T {
return 参数;
}1
2
3
2
3
<T>:声明类型参数,T是一个占位符,代表"某种类型"参数: T:参数类型使用T返回值: T:返回值类型也使用T- 调用时可以用
<具体类型>显式指定,也可以让TypeScript自动推断
示例
TypeScript
function pushArr<T>(arr: T[], item: T): T[] {
arr.push(item);
return arr;
}
// 使用方式1:显式指定类型
const numArr = [1, 2, 3];
pushArr<number>(numArr, 4); // ✅ 返回 number[]
// 使用方式2:类型自动推断
const strArr = ["a", "b"];
pushArr(strArr, "c"); // ✅ 自动推断为 string[]1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
参数命名约定
虽然泛型参数可以使用任意名称,但社区有一些常用约定:
| 参数名 | 含义 | 使用场景 |
|---|---|---|
| T | Type | 最常用的通用类型参数 |
| U, V | 第二、第三个类型 | 多个类型参数时使用 |
| K | Key | 对象键类型 |
| V | Value | 对象值类型 |
| E | Element | 集合元素类型 |
| R | Result | 函数返回值类型 |
TypeScript
// T Type 通用类型
function fn<T>(val: T): T {
return val;
}
// U、V 第二、第三个类型
function two<T, U>(a: T, b: U): [T, U] {
return [a, b];
}
// K Key 对象键
function getKey<T extends object, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
// V Value 对象值
type Dict<K, V> = Record<K, V>;
// E Element 元素类型
function getArr<E>(list: E[]): E[] {
return list;
}
// R Result 返回值类型
function req<T, R>(data: T): R {
return data as unknown as R;
}
// 高频组合(工作常用)
// T=对象,K=键,V=值,R=最终返回
function transForm<T, K extends keyof T, V, R>(obj: T, key: K, val: V): R {
obj[key] = val as never;
return obj as unknown as R;
}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
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
泛型与any的对比
| 特性 | 泛型 | any |
|---|---|---|
| 类型检查 | 编译时完整检查 | 完全跳过检查 |
| IDE智能提示 | 完整支持 | 无提示 |
| 重构安全性 | 自动更新相关类型 | 可能遗漏错误 |
| 运行时开销 | 无(编译后擦除) | 无 |
TypeScript
// 使用 any:IDE无法提供任何帮助
function processAny(data: any): any {
return data.value; // 拼写错误也不会报错
}
// 使用泛型:完整的类型检查和智能提示
function processGeneric<T extends { value: number }>(data: T): number {
return data.value; // IDE自动补全,拼写错误会报错
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
泛型函数
单类型参数
TypeScript
/**
* 恒等函数:返回与输入相同类型和值
* 常用于类型断言或测试场景
*/
function identity<T>(arg: T): T {
return arg;
}
// 使用示例
const str = identity<string>("hello"); // str: string
const num = identity(42); // num: number(自动推断)1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
多类型参数
TypeScript
/**
* 交换元组中两个元素的位置
* @param tuple - 包含两个不同类型元素的元组
* @returns 元素位置交换后的新元组
*/
function swapGeneric<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]];
}
// 使用示例
const swapped = swapGeneric<string, number>(["text", 123]);
// swapped: [number, string]
console.log(swapped); // [123, "text"]
// 类型推断
const inferred = swapGeneric(["hello", true]);
// inferred: [boolean, string]1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
泛型函数作为参数
TypeScript
/**
* 高阶函数:接收一个函数和初始值,执行两次该函数
*/
function applyTwice<T>(fn: (arg: T) => T, initialValue: T): T {
return fn(fn(initialValue));
}
// 使用示例
const double = (x: number) => x * 2;
const result = applyTwice(double, 2); // 8 (2 -> 4 -> 8)1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
箭头函数中的泛型
TypeScript
// 泛型箭头函数
const identity = <T>(arg: T): T => arg;
// 在TSX文件中需要使用 extends 避免与JSX标签混淆
const identityTsx = <T extends unknown>(arg: T): T => arg;1
2
3
4
5
2
3
4
5
泛型约束(extends)
基本约束
默认情况下,泛型参数可以是任意类型。有时我们需要限制类型范围:
TypeScript
interface HasLength {
length: number;
}
/**
* 获取具有length属性的值的长度
* 约束T必须包含length属性
*/
function logLength<T extends HasLength>(arg: T): number {
return arg.length;
}
logLength("hello"); // ✅ string有length
logLength([1, 2, 3]); // ✅ array有length
logLength({ length: 10 }); // ✅ 自定义对象有length
logLength(42); // ❌ number没有length1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
使用类型参数约束
TypeScript
/**
* 安全获取对象属性
* K被约束为T的键
*/
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = {
id: 1,
name: "Alice",
email: "alice@example.com"
};
const userName = getProperty(user, "name"); // ✅ string
const userId = getProperty(user, "id"); // ✅ number
getProperty(user, "age"); // ❌ "age"不是user的属性1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
多重约束
TypeScript
interface Named {
name: string;
}
interface Aged {
age: number;
}
/**
* 合并两个对象,要求T同时满足Named和Aged
*/
function mergeWithInfo<T extends Named & Aged>(obj: T): string {
return `${obj.name} is ${obj.age} years old`;
}
mergeWithInfo({ name: "Alice", age: 25 }); // ✅
mergeWithInfo({ name: "Bob" }); // ❌ 缺少age1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
泛型接口
基本用法
TypeScript
/**
* 通用API响应接口
*/
interface ApiResponse<T> {
data: T;
status: number;
message: string;
timestamp: Date;
}
// 用户API响应
interface User {
id: string;
name: string;
email: string;
}
const userResponse: ApiResponse<User> = {
data: { id: "1", name: "Alice", email: "alice@example.com" },
status: 200,
message: "Success",
timestamp: new Date()
};
// 列表API响应
const userListResponse: ApiResponse<User[]> = {
data: [
{ id: "1", name: "Alice", email: "alice@example.com" },
{ id: "2", name: "Bob", email: "bob@example.com" }
],
status: 200,
message: "Success",
timestamp: new Date()
};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
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
函数类型接口
TypeScript
/**
* 通用事件处理器接口
*/
interface EventHandler<TPayload, TResult = void> {
(payload: TPayload): TResult;
}
// 使用示例
type ClickHandler = EventHandler<MouseEvent, boolean>;
type DataHandler = EventHandler<{ data: any }, Promise<void>>;1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
索引类型接口
TypeScript
/**
* 通用存储接口
*/
interface Storage<T> {
[key: string]: T;
}
// 配置存储
type ConfigValue = string | number | boolean;
type ConfigStorage = Storage<ConfigValue>;
const appConfig: ConfigStorage = {
apiUrl: "https://api.example.com",
timeout: 5000,
debug: true
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
泛型类
基本泛型类
TypeScript
/**
* 通用队列类
*/
class Queue<T> {
private items: T[] = [];
enqueue(item: T): void {
this.items.push(item);
}
dequeue(): T | undefined {
return this.items.shift();
}
peek(): T | undefined {
return this.items[0];
}
get length(): number {
return this.items.length;
}
isEmpty(): boolean {
return this.items.length === 0;
}
}
// 使用示例
const numberQueue = new Queue<number>();
numberQueue.enqueue(1);
numberQueue.enqueue(2);
console.log(numberQueue.dequeue()); // 1
const stringQueue = new Queue<string>();
stringQueue.enqueue("hello");
console.log(stringQueue.peek()); // "hello"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
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
泛型类继承
TypeScript
/**
* 基础集合类
*/
class Collection<T> {
constructor(protected items: T[] = []) {}
add(item: T): void {
this.items.push(item);
}
getAll(): T[] {
return [...this.items];
}
}
/**
* 可搜索的集合类
*/
class SearchableCollection<T> extends Collection<T> {
constructor(
items: T[] = [],
private searchFn: (item: T, query: string) => boolean
) {
super(items);
}
search(query: string): T[] {
return this.items.filter(item => this.searchFn(item, query));
}
}
// 使用示例
interface Product {
id: number;
name: string;
price: number;
}
const productCollection = new SearchableCollection<Product>(
[
{ id: 1, name: "Laptop", price: 999 },
{ id: 2, name: "Phone", price: 599 }
],
(product, query) => product.name.toLowerCase().includes(query.toLowerCase())
);
console.log(productCollection.search("lap")); // [{ id: 1, name: "Laptop", price: 999 }]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
42
43
44
45
46
47
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
42
43
44
45
46
47
泛型工具类型
内置工具类型
TypeScript内置了多个基于泛型的工具类型:
TypeScript
interface User {
id: number;
name: string;
email: string;
age: number;
}
// Partial:所有属性变为可选
type PartialUser = Partial<User>;
// { id?: number; name?: string; email?: string; age?: number; }
// Required:所有属性变为必选
type RequiredUser = Required<User>;
// Readonly:所有属性变为只读
type ReadonlyUser = Readonly<User>;
// Pick:选择部分属性
type UserPreview = Pick<User, "id" | "name">;
// { id: number; name: string; }
// Omit:排除部分属性
type UserWithoutAge = Omit<User, "age">;
// { id: number; name: string; email: string; }
// Record:创建键值对类型
type UserMap = Record<string, User>;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
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
自定义工具类型
TypeScript
/**
* 深度可选:递归地将所有属性变为可选
*/
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
/**
* 提取函数的返回值类型
*/
type ExtractReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
/**
* 提取函数的参数类型
*/
type ExtractParameters<T> = T extends (...args: infer P) => any ? P : never;
/**
* 将对象的所有属性变为非空
*/
type NonNullable<T> = {
[P in keyof T]: Exclude<T[P], null | undefined>;
};
// 使用示例
interface Config {
server: {
host: string;
port: number;
};
database: {
url: string;
timeout?: number;
};
}
type DeepPartialConfig = DeepPartial<Config>;
// 所有嵌套属性都变为可选
function fetchData(): Promise<{ id: number; name: string }> {
return Promise.resolve({ id: 1, name: "test" });
}
type Data = ExtractReturnType<typeof fetchData>;
// Promise<{ id: number; name: string; }>
type AwaitedData = Awaited<Data>;
// { id: number; name: string; }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
42
43
44
45
46
47
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
42
43
44
45
46
47
条件类型与泛型
条件类型基础
TypeScript
/**
* 根据类型判断返回不同类型
*/
type IsString<T> = T extends string ? true : false;
type A = IsString<"hello">; // true
type B = IsString<number>; // false
/**
* 非空类型检查
*/
type NonNullable<T> = T extends null | undefined ? never : T;
type C = NonNullable<string | null>; // string1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
infer关键字
infer用于在条件类型中推断类型:
TypeScript
/**
* 提取数组元素类型
*/
type ElementType<T> = T extends (infer E)[] ? E : never;
type NumArray = ElementType<number[]>; // number
type StrArray = ElementType<string[]>; // string
/**
* 提取Promise返回值类型
*/
type PromiseResult<T> = T extends Promise<infer R> ? R : T;
type AsyncResult = PromiseResult<Promise<string>>; // string
type SyncResult = PromiseResult<number>; // number
/**
* 提取函数的第一个参数类型
*/
type FirstParam<T> = T extends (first: infer F, ...rest: any[]) => any ? F : never;
function example(name: string, age: number): void {}
type First = FirstParam<typeof example>; // string1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

