三块面板:TS 源码 / 写死的 tsc 报错样本(非现场运行)/ 可点跑的等价 JS。泛型 = 类型的参数,让返回类型跟着输入形状变,不像 any 那样把信息漏光。

一、同一个函数:先写成 any,再写成泛型

TS 源码

// any 版:进去知道是 number[],出来啥都不知道
function firstAny(arr: any[]): any { return arr[0] }
const x = firstAny([1, 2, 3])   // x: any —— 丢了元素类型

// 泛型版:类型进函数又活着出来
function first<T>(arr: T[]): T { return arr[0] }
const a = first([1, 2, 3])      // T 推断成 number,a: number
const b = first(['x', 'y'])     // T 推断成 string,b: string
a.toUpperCase()                 // ✗ a 是 number,没有 toUpperCase
first.ts:8:3 - error TS2339: Property 'toUpperCase' does not exist on type 'number'. 8 a.toUpperCase() ~~~~~~~~~~~
为什么报:泛型把实参 number[] 的 T 填成 number,返回值也就精确成 number——正因为“信息没丢”,在 number 上调 string 方法才会被抓出来。any 版这里根本不报,错误被推迟到运行时。

等价 JS(去掉类型后可真跑)

[迷你控制台]

二、泛型约束 extends:给 T 划范围才能访问成员

function longest<T extends { length: number }>(a: T, b: T): T {
  return a.length >= b.length ? a : b   // ✓ 约束保证有 length
}
longest('abc', 'a')     // ✓ string 有 length
longest(1, 2)           // ✗ number 没有 length,被约束挡下
longest.ts:5:12 - error TS2345: Argument of type 'number' is not assignable to parameter of type '{ length: number; }'. 5 longest(1, 2) ~
为什么报:T extends { length: number } 既让函数内部敢访问 length,又在调用点拦住不合格的实参——约束是泛型的“边界”两面。

三、默认类型参数

function box<T = string>(v: T): { value: T } { return { value: v } }
box('a')   // T = string
box(42)    // T = number(实参优先于默认)

四、什么时候用泛型,什么时候别

// 用泛型:输入类型与输出类型之间存在“形状依赖”
function identity<T>(v: T): T { return v }        // 给啥类型就按同样形状返回
function pair<A, B>(a: A, b: B): [A, B] { return [a, b] }

// 别用泛型:结果与入参无关,或只是“几种固定情况之一”——那用联合类型
type Status = 'idle' | 'loading' | 'error'         // 联合即可,别硬写成泛型
分寸:泛型的价值在“复用代码又不牺牲信息”。当返回类型跟着入参类型变时用泛型;只是几个枚举值就用第五章的联合。别为显得高级把简单联合套成泛型——可读性是要付账的。