3.4 泛型基础


3.4 泛型基础

这节是"语言结构"的最后一站,也是通往第五章高级类型的大门。泛型让你把类型当参数传,写出"不绑定具体类型、但又能保持类型安全"的代码。第二章讲的是契约怎么判定,泛型讲的是契约怎么被复用。

为什么需要泛型:从重复到参数化

没有泛型,处理多种类型的容器要重写或退回 any

// 不用泛型:每种类型一套,或放弃检查用 any function firstNumber(a: number[]): number | undefined { return a[0]; } function firstString(a: string[]): string | undefined { return a[0]; } // 用泛型:一份实现覆盖所有元素类型 function first<T>(a: T[]): T | undefined { return a[0]; } const n = first([1, 2, 3]); // n: number | undefined const s = first(["a", "b"]); // s: string | undefined

输入输出:first([1,2,3]) 推出 number|undefinedfirst(["a"]) 推出 string|undefined。泛型没丢安全,只是让 T 跟随传入的参数推断。这比 any 强,比重写 N 份省。

多个类型参数与约束

泛型可多个,并用 extends 约束它必须具备某些形状。

// 两个参数:键与值 function pair<K, V>(k: K, v: V): [K, V] { return [k, v]; } // 约束 T 必须有 length 属性 interface HasLength { length: number; } function longest<T extends HasLength>(a: T, b: T): T { return a.length >= b.length ? a : b; } longest("abc", "de"); // OK // longest(1, 2); // 报错:number 没有 length

背景:想写"取较长者"但又不限类型。操作:约束 T extends HasLength。结果:字符串、数组都能用,数字不行。解读:约束是泛型的护栏,防止你调用 T 上不存在的成员。

泛型接口与泛型类

类型本身也能参数化,得到可复用的"形状模板"。

interface ApiList<T> { total: number; items: T[]; } const users: ApiList<{ id: number; name: string }> = { total: 1, items: [{ id: 1, name: "张三" }], }; class Stack<T> { private data: T[] = []; push(v: T) { this.data.push(v); } pop(): T | undefined { return this.data.pop(); } }

输入输出:ApiList<User> 检查 items 每一项都是 User 形状;Stack<number> 保证只压数字、弹出数字。泛型把"容器里装什么"也纳入契约。

默认类型参数

泛型可给默认,调用方省略时自动用默认。

type Box<T = string> = { value: T }; const a: Box = { value: "默认字符串" }; // T 推断为 string const b: Box<number> = { value: 42 }; // 显式覆盖

下图标出泛型如何"带着类型信息穿过函数":

03-04-fig01-2

实战:通用的仓储接口

interface Repository<T> { findById(id: number): T | null; save(entity: T): void; all(): T[]; } // 对用户、订单复用同一套契约 interface UserRepo extends Repository<{ id: number; name: string }> {} interface OrderRepo extends Repository<{ id: number; amount: number }> {}

泛型约束 + keyof:类型安全的取属性

把"约束"和"键集合"结合,能写出"从对象取某键的值"且全程保类型的工具,这是第五章映射类型的地基。

function pluck<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { id: 1, name: "张三", active: true }; const id = pluck(user, "id"); // number const name = pluck(user, "name"); // string // pluck(user, "age"); // 报错:age 不是 user 的键 // 多个键:返回对应值的元组 function pluckMany<T, K extends keyof T>(obj: T, keys: K[]): T[K][] { return keys.map((k) => obj[k]); } const vals = pluckMany(user, ["id", "name"]); // (number | string)[]

背景:通用工具要从对象按键取值,又不想退化为 any。操作:用 K extends keyof T 把键锁在对象键集合内。结果:返回值类型自动是 T[K],精准跟随键。解读:keyof 与索引访问 T[K] 是泛型约束里最高频的组合,提前熟悉第五章会顺。

泛型函数的重载

当同一泛型函数对不同类型的处理分支差异大,可用重载给出更精准的签名。

function identity<T>(v: T): T; function identity(v: any): any { return v; } const a = identity(1); // 1 的推断为 number const b = identity("x"); // string // 更实用的:按是否为数组返回不同形状 function wrap<T>(v: T): T[]; function wrap<T>(v: T[]): T[]; function wrap<T>(v: T | T[]): T[] { return Array.isArray(v) ? v : [v]; } const w1 = wrap(5); // number[] const w2 = wrap([5]); // number[]

背景:入参可能是单值或数组,希望统一返回数组。操作:重载区分单值/数组两种签名。结果:调用点都拿到 T[],内部用 Array.isArray 收窄。解读:重载让泛型在"形态不同"时也能给出干净的类型,是库函数常用手法。

工程取舍:泛型 vs 联合 vs any

能用具体类型就别泛型;当"一套逻辑服务多种类型且都要保安全"时泛型正解;联合适合"固定几种已知候选";any 是放弃了安全,仅在迁移过渡期短暂出现。泛型写多了会增加阅读难度,团队要对复杂度有共识,别为了炫而泛型。

进阶:条件分发与泛型函数重载

泛型遇上联合会触发分布式条件(见第五章),而在函数层面,泛型重载能让"不同入参推断不同出参"更顺。

// 泛型 + 重载:根据是否有默认值,推断返回是否可空 function get<T>(arr: T[], i: number): T | undefined; function get<T>(arr: T[], i: number, fallback: T): T; function get<T>(arr: T[], i: number, fallback?: T): T | undefined { return i >= 0 && i < arr.length ? arr[i] : fallback; } const v1 = get([1, 2], 5); // number | undefined const v2 = get([1, 2], 5, 0); // number(有兜底,无 undefined)

输入输出:v1 因无兜底带 undefinedv2 因给了 fallback 推出精确 number。泛型把"同一个逻辑、不同安全档位"表达清楚,调用点推断贴合实际分支。

实战:泛型约束配合 keyof 取值

keyof 与泛型约束组合,能写出"按键取值且返回类型跟随键"的工具,编译期就锁住键名合法。

function pluck<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } const user = { id: 1, name: "张三", active: true }; const id = pluck(user, "id"); // number const name = pluck(user, "name"); // string // pluck(user, "age"); // 报错:age 不是 user 的键

背景:想从对象按字符串键取值又不想退回 any。操作:K extends keyof T 约束键。结果:返回值类型精确为 T[K],拼错键立即报错。解读:这是第五章映射/索引访问的雏形,也是日常最常用的泛型模式之一。

本节要点回顾

  • 泛型把类型当参数,复用逻辑同时保留安全
  • extends 给泛型加约束,防止误用成员
  • 接口、类、类型别名都能参数化
  • 默认类型参数让调用更省心

⚠️ 别把泛型当 any 的漂亮替代品然后到处堆 <T, U, V, W>。泛型层数越深阅读成本越高,超过两参要问问是否真需要。

💡 写工具函数时先想"它服务的类型是否有限且已知",是就用联合,否才上泛型;泛型约束尽量用 extends 收窄能力面,比裸 T 更安全也好推。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U