TypeScript 泛型(Generics)详解及示例

1. 什么是泛型?

泛型是 TypeScript 中创建可重用组件的重要工具,它允许我们创建可以处理多种类型而不是单一类型的组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 不使用泛型 - 只能处理特定类型
function identityNumber(arg: number): number {
return arg;
}

function identityString(arg: string): string {
return arg;
}

// 使用泛型 - 可以处理任意类型
function identity<T>(arg: T): T {
return arg;
}

2. 基础泛型语法

泛型函数

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

// 调用方式
let output1 = identity<string>("hello"); // 显式指定类型
let output2 = identity("world"); // 类型推断

泛型接口

1
2
3
4
5
6
7
8
// 泛型接口
interface GenericIdentityFn<T> {
(arg: T): T;
}

// 使用泛型接口
let myIdentity: GenericIdentityFn<number> = identity;

泛型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;

constructor(zeroValue: T, add: (x: T, y: T) => T) {
this.zeroValue = zeroValue;
this.add = add;
}
}

// 使用数字类型
let numberInstance = new GenericNumber<number>(0, (x, y) => x + y);

// 使用字符串类型
let stringInstance = new GenericNumber<string>("", (x, y) => x + y);

3. 泛型约束

使用 extends 约束

1
2
3
4
5
6
7
8
9
10
11
12
13
interface Lengthwise {
length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
console.log(arg.length); // 现在可以访问 .length 属性
return arg;
}

loggingIdentity("hello"); // 正常,字符串有 length 属性
loggingIdentity([1, 2, 3]); // 正常,数组有 length 属性
// loggingIdentity(3); // 错误,数字没有 length 属性

多类型约束

1
2
3
4
5
6
7
8
9
10
11
12
13
function copyFields<T extends U, U>(
target: T,
source: U
): T {
for (let id in source) {
target[id] = (source as any)[id];
}
return target;
}

let x = { a: 1, b: 2, c: 3, d: 4 };
copyFields(x, { b: 10, d: 20 });

4. 泛型在类中的高级用法

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
// 泛型栈实现
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];
}

size(): number {
return this.items.length;
}
}

// 使用
const numberStack = new Stack<number>();
numberStack.push(1);
numberStack.push(2);
console.log(numberStack.pop()); // 2

const stringStack = new Stack<string>();
stringStack.push("hello");
stringStack.push("world");

5. 泛型与接口结合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 泛型接口
interface KeyValuePair<K, V> {
key: K;
value: V;
}

// 使用泛型接口
let pair1: KeyValuePair<number, string> = { key: 1, value: "one" };
let pair2: KeyValuePair<string, boolean> = { key: "isValid", value: true };

// 泛型函数接口
interface Transformer<T, U> {
(input: T): U;
}

// 使用
const stringToNumber: Transformer<string, number> = (str) => parseInt(str);
const numberToString: Transformer<number, string> = (num) => num.toString();

6. 条件类型与泛型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 条件类型
type IsString<T> = T extends string ? true : false;

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

// 条件类型与泛型函数
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
"object";

function getTypeName<T>(arg: T): TypeName<T> {
return typeof arg as TypeName<T>;
}

const result1 = getTypeName("hello"); // "string"
const result2 = getTypeName(42); // "number"

7. 泛型工具类型

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
// Partial - 所有属性变为可选
interface User {
name: string;
age: number;
}

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

// Readonly - 所有属性变为只读
type ReadonlyUser = Readonly<User>;

// Pick - 选择部分属性
type UserName = Pick<User, "name">;

// Record - 创建对象类型
type PageInfo = Record<"home" | "about" | "contact", { title: string }>;

// 使用
const pages: PageInfo = {
home: { title: "首页" },
about: { title: "关于" },
contact: { title: "联系" }
};

8. 实际应用示例

API 响应处理

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
interface ApiResponse<T> {
success: boolean;
data: T;
message?: string;
}

interface User {
id: number;
name: string;
email: string;
}

interface Product {
id: number;
title: string;
price: number;
}

// 模拟 API 调用
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return response.json();
}

// 使用
const userResponse = await fetchData<User>("/api/users/1");
const productResponse = await fetchData<Product>("/api/products/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
class DataProcessor<T> {
private data: T[];

constructor(data: T[]) {
this.data = data;
}

filter(predicate: (item: T) => boolean): DataProcessor<T> {
return new DataProcessor(this.data.filter(predicate));
}

map<U>(mapper: (item: T) => U): DataProcessor<U> {
return new DataProcessor(this.data.map(mapper));
}

get(): T[] {
return this.data;
}
}

// 使用
const numbers = [1, 2, 3, 4, 5];
const result = new DataProcessor(numbers)
.filter(n => n > 2)
.map(n => n * 2)
.get(); // [6, 8, 10]

9. 泛型最佳实践

  1. 使用有意义的泛型参数名
1
2
3
// 好的命名
function identity<Type>(arg: Type): Type { return arg; }
function processData<Input, Output>(input: Input): Output { /* ... */ }
  1. 适当使用约束
1
2
3
4
// 需要时使用约束
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
  1. 避免过度使用泛型:只在真正需要灵活性时使用。

泛型大大增强了 TypeScript 的类型安全性和代码重用性,是现代 TypeScript 开发中的重要特性。