课程库 › TypeScript › TypeScript 起步 › 第 6 课
泛型初见 —— 会传染的类型
普通课约 50 分钟
本课导读
单元最后一个语法概念,也是名声最"劝退"的一个——泛型。先说定心丸:本课目标是"够用直觉版"——你要会的是读懂带 <T> 的函数签名、享受它带来的自动推断;至于自己设计复杂泛型,那是框架作者的活,不是使用者的。用一个真实痛点引入,你会发现泛型的动机朴素得可爱。预计 50 分钟。
痛点:想通用,又不想裸奔
写一个"取数组第一个元素"的函数。数字数组要用,字符串数组也要用——类型怎么标?
运行看看:n.toUpperCase() 编译一路绿灯,运行时才爆炸——any 等于把第一课的惨案又请了回来。两难摆在面前:要通用(一个函数服务所有类型)还要保护(进什么类型,出口就该知道是什么类型)。
泛型:类型的占位符
拆解 function first<T>(arr: T[]): T:
<T>——声明一个类型占位符:T 代表"某种类型,调用时才定"arr: T[]和: T——占位符在参数和返回值里呼应:进来"T 的数组",出去"一个 T"- 调用时不用写 T 是什么:传
number[],TS 自动推断 T=number——推断又一次替你干活 - 效果:一个函数,通吃所有类型,且每次调用都有完整类型保护——两难全解
提示
类比记忆:泛型是合同里的"甲方"。合同模板写"甲方交货给甲方"——签约时甲方是谁(T 是什么)才确定,但两处"甲方"必是同一人,这是模板保证的。所以叫"会传染的类型":进口是谁,出口就是谁,传染路径写在签名里。
其实你已经用了三课泛型
揭个底——泛型不是新朋友,是一直没自我介绍的老同事:
number[]就是Array<number>的简写——你第二课就在用泛型map为什么能"加工成什么类型,新数组就是什么类型的数组"?签名里的<U>在传染- 将来你会遇到
Promise<string>("将来会交付一个 string")——同一个模式。读法通用:尖括号里的,就是被传染的那个类型
使用者的本分:会读、会享受
本课程对泛型的定位说清楚——你现阶段需要的能力是两条:
- 会读签名:看到
function pick<T>(arr: T[], count: number): T[],能读出"从 T 的数组里挑 count 个,返回还是 T 的数组" - 会享受推断:调用时正常传参,让 TS 自己算 T——需要手写
pick<string>(...)的场合极少
自己写泛型,只在一种场合动手:函数里出现了"进出类型要一致,但具体是什么无所谓"的模式(就像 first)。超过这个复杂度(多个占位符、约束、条件类型)——那是库作者的领域,用到再学,不要预习。
✍️ 练习
练习 1:读签名
不运行,先把三个签名各翻译成一句人话(答案对照):
① function last<T>(arr: T[]): T
② function repeat<T>(item: T, times: number): T[]
③ function zip<A, B>(a: A[], b: B[]): Array<A | B>
✅ 参考答案
① 收一个 T 的数组,返回其中一个 T(取最后一个元素)。② 收一个 T 和次数,返回 T 的数组(把 item 重复 times 次)。③ 两个占位符:收 A 的数组和 B 的数组,返回"A 或 B"的数组(混装)——占位符可以有多个,各自独立传染。能把 ③ 读出来,你的泛型阅读力已经超过本课程的要求线。
练习 2:写一个 last
实现练习 1 的 ①:last<T>(arr: T[]): T 取最后一个元素。分别用数字数组和字符串数组调用,并验证返回值保有各自的类型能力。
✅ 参考答案
function last<T>(arr: T[]): T {
return arr[arr.length - 1];
}
console.log(last([599, 149, 1599]) * 2);
console.log(last(["外设", "热销"]).toUpperCase());
和 first 一模一样的模式:<T> 声明、T[] 进、T 出。写过一次,这个模式就是你的了。
练习 3:找出裸奔处(挑战)
下面的工具函数用了 any,导致第 11 行的事故编译时没被拦住。把它改成泛型版,让事故在编译期现形,然后修复调用。
✅ 参考答案
function wrapInArray<T>(item: T): T[] {
return [item];
}
const wrapped = wrapInArray(599); // T=number → wrapped 是 number[]
const result = wrapped[0] * 2; // 泛型版下,.toUpperCase() 会当场 TS2339
console.log(result);
any → 泛型的改造三步:<T> 声明、any 换 T、坐等推断接管。这也是审查 AI 生成代码的一个检查点:工具函数里的 any,多数该是泛型。
📝 随堂测验
1. 用 any 实现“通用函数”的代价是?
2. function first<T>(arr: T[]): T 里的 T 是?
3. 调用 first([599, 149]) 时没写 <number>,为什么类型仍然全对?
4. number[] 与 Array<number> 的关系是?
5. 本课程对“自己写泛型”的边界建议是?
本课小结
- 泛型解决"通用 vs 保护"两难:
<T>占位、签名内呼应传染、调用时自动推断 - 你早在用泛型:
number[]=Array<number>、map 的加工传染、将来的Promise<T> - 使用者本分两条:会读尖括号签名、会享受推断;只在 first/last 式模式下亲手写
- 审 AI 代码新检查点:工具函数里的 any 多数该是泛型
语法零件全部集齐。下一课项目实战:类型安全的待办清单——interface、字面量联合、收窄、泛型数组方法一次合体。
划选正文任意文字可高亮、批注或加入复习卡
讨论
载入中…