第 1 节 概述
上一篇我们学了类。类让你把行为和数据打包。但问题是——如果一段逻辑可以适用于多种类型,怎么既保持复用性,又保持类型安全?
比如一个函数取数组的第一个元素:
1
2
3
| function first(arr: number[]): number | undefined {
return arr[0]
}
|
这个函数只能处理 number[]。如果输入 string[],返回类型就不好办了——返回 number | undefined 不对,写两个函数又太啰嗦。
泛型就是解决这个问题的。它允许你定义一个"类型参数",调用时再确定具体类型。
第 2 节 泛型函数
1
2
3
4
5
6
7
8
9
10
11
| function first<T>(arr: T[]): T | undefined {
return arr[0]
}
// 调用时指定类型
const num = first<number>([1, 2, 3]) // number | undefined
const str = first<string>(["a", "b"]) // string | undefined
// 也可以不写,TypeScript 会自动推断
const num = first([1, 2, 3]) // 推断为 number | undefined
const str = first(["a", "b"]) // 推断为 string | undefined
|
<T> 是类型参数,习惯用大写单字母命名:T(Type)、K(Key)、V(Value)、E(Element)。
1
2
3
4
5
6
7
| // 交换两个变量的值
function swap<T, U>(pair: [T, U]): [U, T] {
return [pair[1], pair[0]]
}
const swapped = swap(["hello", 42])
// swapped 的类型为 [number, string]
|
第 3 节 泛型约束
有时候你不希望 T 是"任何类型",而是"必须有某些属性"的类型。用 extends 添加约束:
1
2
3
4
5
6
7
8
9
10
11
12
13
| interface HasLength {
length: number
}
// T 必须具有 length 属性
function logLength<T extends HasLength>(arg: T): T {
console.log(arg.length)
return arg
}
logLength("hello") // ✅ string 有 length
logLength([1, 2, 3]) // ✅ array 有 length
logLength(42) // ❌ number 没有 length
|
另一个常见模式——约束键的类型:
1
2
3
4
5
6
7
8
9
| function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
const person = { name: "张三", age: 18, email: "z@example.com" }
getProperty(person, "name") // ✅ 返回 string
getProperty(person, "age") // ✅ 返回 number
getProperty(person, "weight") // ❌ "weight" 不在 keyof T 中
|
第 4 节 泛型接口
接口也可以带类型参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| interface ApiResponse<T> {
code: number
message: string
data: T
}
// 不同接口返回不同的 data 类型
type UserResponse = ApiResponse<{ id: number; name: string }>
type ListResponse = ApiResponse<string[]>
const userRes: UserResponse = {
code: 200,
message: "成功",
data: { id: 1, name: "张三" },
}
|
泛型接口描述函数类型也很常见:
1
2
3
4
5
6
7
| interface Callback<T> {
(data: T): void
}
function fetchData<T>(url: string, cb: Callback<T>) {
// ...
}
|
第 5 节 泛型类
类和接口一样,可以带有类型参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| class Box<T> {
private content: T
constructor(value: T) {
this.content = value
}
getValue(): T {
return this.content
}
setValue(value: T): void {
this.content = value
}
}
const numberBox = new Box<number>(42)
numberBox.getValue() // number
const stringBox = new Box<string>("hello")
stringBox.getValue() // string
|
第 6 节 多个泛型参数
需要两个独立类型时,可以用多个参数:
1
2
3
4
5
6
7
| function merge<T, U>(obj1: T, obj2: U): T & U {
return { ...obj1, ...obj2 }
}
const merged = merge({ name: "张三" }, { age: 18 })
// merged 的类型为 { name: string } & { age: number }
// 等价于 { name: string; age: number }
|
多个参数各自可以有约束:
1
2
3
4
5
6
7
8
9
10
11
| function pair<T extends string | number, U extends string | number>(
first: T,
second: U,
): [T, U] {
return [first, second]
}
pair(1, 2) // ✅
pair("a", "b") // ✅
pair(1, "b") // ✅
pair(true, 1) // ❌ boolean 不在约束中
|
第 7 节 泛型 vs any
泛型不是 any,两者有本质区别:
1
2
3
4
5
6
7
8
9
10
11
| // 使用 any —— 失去了类型安全
function firstAny(arr: any[]): any {
return arr[0]
}
const x: string = firstAny([1, 2, 3]) // 不报错,但 x 实际上是 number,不安全
// 使用泛型 —— 保持类型关系
function firstGen<T>(arr: T[]): T | undefined {
return arr[0]
}
const y: string = firstGen([1, 2, 3]) // ❌ 类型错误,T 被推断为 number
|
| 对比 | 泛型 | any |
|---|
| 输入与输出的关系 | 保持精确对应 | 切断联系 |
| 类型安全 | ✅ 编译期检查 | ❌ 无检查 |
| 使用场景 | 逻辑通用但类型需关联 | 逃生舱(极少用) |
TypeScript 还内置了 Partial、Pick、Omit、Record 等工具类型,进阶篇中会系统介绍。
泛型是 TypeScript 的"高级关卡"——学会了它,你的类型水平会上一个新台阶。
但泛型也带来一个新问题:当类型在运行时变得不确定时,怎么确保我们安全地使用它们?下一篇讲类型守卫——运行时安全的第一道防线。