泛型
泛型函数与接口、约束 extends。
泛型
这一课你将能做到
- 写出带类型参数的泛型函数
<T>,并理解类型参数如何被推断 - 定义泛型接口和泛型类,构建可复用的类型化容器
- 使用泛型约束
extends和默认类型参数,处理「限制但不写死」的场景
为什么要学
前面学的类型都是「具体类型」:string、number、User。但真实世界里大量逻辑是通用的:一个 first() 函数该返回数组第一个元素——数组是 number[] 还是 User[] 无所谓,逻辑都一样。如果给每种数组都写一个版本,就是重复造轮子。
泛型(Generics)就是解决这个问题:把类型当成参数传进函数/接口/类,让同一段逻辑适配任意类型,同时保留类型信息。
没有泛型的 TS,很多代码你只能写成 any[]、Promise<any>、useState<any>,类型系统瞬间退化成摆设。学会泛型,你才真正能用 TS 写出既灵活又安全的代码。
先建立图像
泛型的直觉:函数接收「值参数」,泛型函数还额外接收「类型参数」。
普通函数: function add(a, b) => 传入两个值,返回一个值
泛型函数: function first<T>(arr: T[]) => 传入一个数组 + 一个类型 T
T 由调用方决定
返回类型是 T | undefined
first<number>([1, 2, 3]) T = number, 返回 number | undefined
first<string>(["a", "b"]) T = string, 返回 string | undefined
first([true, false]) 编译器自动推断 T = boolean
跟着做
新建 generics-demo.ts,逐步实操。
步骤 1:第一个泛型函数
// 没有泛型:只能处理 number 数组
function firstNumber(arr: number[]): number | undefined {
return arr[0];
}
// 有泛型:什么数组都能处理
function first<T>(arr: T[]): T | undefined {
return arr[0];
}
const n = first<number>([1, 2, 3]); // n: number | undefined
const s = first<string>(["a", "b"]); // s: string | undefined
const b = first([true, false]); // 自动推断 T = boolean
console.log(n, s, b); // 1 "a" true
<T> 就是类型参数(type parameter)。调用时可以不写,TS 会从实参推断。
步骤 2:多个类型参数
// 两个独立的类型参数
function pair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
const p1 = pair("name", "小明"); // [string, string]
const p2 = pair("age", 25); // [string, number]
console.log(p1, p2); // [ 'name', '小明' ] [ 'age', 25 ]
每个类型参数独立推断,K 和 V 可以相同也可以不同。
步骤 3:泛型约束 extends
类型参数不能乱传,要满足某种「能力」时用 extends 约束:
// 要求 T 必须有 length 属性
interface HasLength {
length: number;
}
function logLength<T extends HasLength>(value: T): T {
console.log(`长度:${value.length}`);
return value;
}
logLength("hello"); // string 有 length,OK
logLength([1, 2, 3]); // array 有 length,OK
// logLength(123); // 报错:number 没有 length