课程库 › TypeScript › TypeScript 起步 › 第 5 课
联合类型与收窄 —— "可能是 A 也可能是 B"怎么安全处理
普通课约 55 分钟
本课导读
真实世界的数据经常"不确定":用户 ID 可能是数字也可能是字符串、可选字段可能压根没给、订单状态是几个词中的一个。TS 对付不确定的武器是一对组合拳:联合类型(用 | 声明"可能是 A 也可能是 B")+ 收窄(用 if 检查把可能性一步步排除,编译器全程跟着你缩小范围)。这是 TS 最有味道的设计——你写普通的判断逻辑,类型自动变准。预计 55 分钟。
联合类型:竖线连起可能性
前两课 string | undefined 已经两次露脸,今天正式介绍。A | B 读作"A 或 B":
声明"可能是 A 或 B"很简单。真正的问题在使用——对一个"可能是两种东西"的值,你能干什么?
报错说得很讲理:toUpperCase 是字符串的方法——万一 id 是数字呢?联合类型的铁律:只能直接使用所有候选类型"共有"的成员(比如 toString 谁都有)。想用某一边独有的能力,得先证明"现在它就是那一边"——这就是收窄。
收窄:用 if 说服编译器
看清这里发生的魔法:
typeof id === "string"是一句普通的运行时判断(typeof 是 JS 老功能)——但 TS 在编译期读懂了它:if 分支里 id 收窄为 string,else 侧自动排除剩 number- 你没写任何类型语法,类型跟着控制流走——这叫控制流分析,TS 的招牌本事
- 上一课"可选字段用前判断 undefined",现在有了正式名分:
if (x !== undefined)就是对string | undefined的收窄
提示
类比记忆:联合类型像安检口的双通道包裹(可能是液体也可能是电子产品),收窄就是过一遍扫描仪——扫完确认是电子产品,才允许按电子产品的规矩处理。不扫就直接拆(不判断就调方法),安检员(编译器)当场拦下。
字面量联合:比 string 更严格的字符串
联合的候选不仅能是类型,还能是具体的值——这是 TS 一个极其高频的实战招式:
两个新知识一次到位:
- 字面量联合
"pending" | "paid" | ...:这个参数不是"任意字符串",而是这四个词之一——状态、档位、模式这类"有限选项"场景的标配。拼成"payed"的惨案(纯 JS 里要线上才发现)从此绝迹 type别名:type 名字 = 类型给任何类型起名复用。和 interface 的分工现阶段这么记:形状用 interface,联合/别名用 type,够用两年
收窄的第三板斧:in
typeof 认基础类型、!== undefined 排除空值,第三板斧对付"两种形状"——in 检查字段在不在:
收窄三板斧集齐:typeof(基础类型)、!== undefined(可选/空值)、in(形状之间)。它们的共同点:都是普通的运行时判断,TS 顺着你的逻辑自动把类型越缩越准——好的 TS 代码,就是把"你脑子里的分类讨论"写成 if,类型系统全程陪跑。
✍️ 练习
练习 1:价格标签
写函数 priceTag(price: number | string):数字则格式化为 "¥599",字符串(如 "面议")则原样加括号返回 "(面议)"。
✅ 参考答案
function priceTag(price: number | string): string {
if (typeof price === "number") {
return "¥" + price;
}
return "(" + price + ")";
}
console.log(priceTag(599));
console.log(priceTag("面议"));
typeof 收窄的最小完整用例:if 里是 number 的世界,if 外是 string 的世界,各用各的写法。
练习 2:会员等级机
定义字面量联合 type Level = "bronze" | "silver" | "gold",写函数 upgrade(level: Level): Level 返回下一级(gold 封顶返回自己)。然后故意用错误的等级词调用一次,观察报错。
✅ 参考答案
type Level = "bronze" | "silver" | "gold";
function upgrade(level: Level): Level {
if (level === "bronze") {
return "silver";
}
if (level === "silver") {
return "gold";
}
return "gold";
}
console.log(upgrade("bronze"));
console.log(upgrade("gold"));
upgrade("diamond") 会报 TS2345——不存在的等级词进不了函数。返回类型标 Level 还有个隐藏福利:函数体里手滑 return "glod" 也会当场被抓。
练习 3:消息分诊台(挑战)
系统消息有两种形状:文本消息 { text: string }、转账消息 { amount: number; from: string }。写 handle(msg) 函数:文本返回 "[文本] ...",转账金额 ≥ 1000 返回 "[大额转账] 来自xxx",否则 "[转账] 来自xxx"。三板斧选对工具。
✅ 参考答案
interface TextMsg {
text: string;
}
interface TransferMsg {
amount: number;
from: string;
}
function handle(msg: TextMsg | TransferMsg): string {
if ("text" in msg) {
return "[文本] " + msg.text;
}
if (msg.amount >= 1000) {
return "[大额转账] 来自" + msg.from;
}
return "[转账] 来自" + msg.from;
}
console.log(handle({ text: "开会啦" }));
console.log(handle({ amount: 5000, from: "老王" }));
console.log(handle({ amount: 200, from: "小陈" }));
形状之间用 in 收窄;进入转账分支后 msg.amount、msg.from 随便点——TS 已确认它是 TransferMsg。这个"消息分诊"模式在真实前端代码里出场率极高(各种事件、通知、API 响应)。
📝 随堂测验
1. number | string 类型的值,不做任何判断直接调 .toUpperCase() 会?
2. if (typeof id === "string") 分支内,id 的类型变成了?
3. type OrderStatus = "pending" | "paid" 这类字面量联合的最大价值是?
4. interface 和 type 的分工,本课程的够用版口诀是?
5. 收窄三板斧和各自的主场是?
本课小结
- 联合类型
A | B:声明不确定;只能直接用共有成员,独家能力先收窄 - 收窄三板斧:
typeof/!== undefined/in——普通判断,类型跟着控制流自动变准 - 字面量联合 +
type别名:把"有限选项"写进类型,错别字状态绝迹;口诀"形状 interface、联合 type" - 可选字段/参数的
| undefined,至此完全收网
下一课最后一块拼图:泛型——当一个函数想对"任何类型"通用、又不想丢掉类型保护时,<T> 登场。
划选正文任意文字可高亮、批注或加入复习卡
讨论
载入中…