想象你是写字楼的新任总务,每天有成百上千个“数据”要进出不同楼层。但楼里没有门禁,谁都能乱窜:有人把“年龄”写成了“名字”,有人把“价格”当“数量”用,出了事故才有人发现放跑了错误的“人”。 这时保安队长提出方案:给每个进入大楼的人办一张通行证,标明身份——这就是 TypeScript 做的事。它给 JavaScript 的数据贴上“类型标签”,让代码在运行之前,就能通过门禁检查揪出那些低级错误。你只需要学会辨认不同类型的“通行卡”,再学会处理“员工卡或访客卡都能进”的联合情况,就能让整栋大楼井然有序。
学懂 TypeScript 基本类型与联合类型,是你从“会写 JS”走向“能驾驭大型前端工程”的第一道门禁,能让协作更安全、重构更自信、Bug 更少。
可视化演示
展示TypeScript类型核心机制:基本类型声明、联合类型分支与类型守卫收窄,可视化编译期类型检查如何提升代码安全性。
本节配有交互动效(进入视口后自动加载,约 3KB)。
基本类型声明 string/number/boolean等基本类型在编译期检查 类型错误在编译阶段而非运行时暴露 let x: type = value
类型系统入门
大楼的保安队长先给你看了一段监控:因为没有门禁,一位“价格”溜进了“姓名”通道,结果收银台把客人的名字打印成了数字。JavaScript 就像这座不设防的大楼——变量随时可以变成任何类型,灵活但危险。
TypeScript 的理念很简单:“数据进门之前,先验明正身。”它会静态地检查每一个变量、函数参数和返回值,在代码运行之前就把类型矛盾暴露出来。这不是运行时监控,而是编译期门禁:只要类型不合规,编译就直接亮红灯。
类型系统本质上是在描述“哪些值属于哪些集合”。这份集合关系可用公式表达:
类型系统不是限制你,而是像门禁一样保护你:它可以告诉你“这扇门只能让 number 进去”,省去运行时飞来飞去的排查。给整栋楼装上这个门禁系统,正是 TS 存在的意义。
- 所有类型集合
\mathcal{T}代表 TypeScript 中一切可能类型的总和,相当于“整栋大楼的通行证总览”。 - 字符串集合
\mathcal{S}是所有字符串的集合,比如"hello"、"123"都属于它。 - 并集运算
\cup表示集合的并集,意思是“这些类型共同组成了类型宇宙”。 - 省略更多
\cdots表示还有布尔值、对象、数组等更多类型集合没有一一列出。
基本类型解剖
门禁系统发了好几款通行卡,颜色不同、权限不同。最基础的是四张“原始卡”:蓝色员工卡 string 存文本,红色访客卡 number 存数字,绿色临时卡 boolean 存真/假,还有很少单独占位的 null 和 undefined 两张“无权限卡”。
除了原始卡,还有“钱包型”的 object 可以装多个身份信息,array 是一叠同类卡片,tuple 是“先姓名后年龄”的固定卡槽。更特殊的是两张“万能卡”:any 表示放弃检查,谁都能刷;unknown 则是一张“待验证卡”,必须经过检查才能确定身份。
这些类型可以看作一个卡片集合:
理解了这些卡片,你写 let age: number = 30 时,就像给变量发了一张红色访客卡——别人再也不能往里面塞 "三十" 这个蓝色字符串了。保安看到卡色,就知道该放你进哪扇门。
- 卡片分类函数
\text{Card}表示“把值分类到哪种卡”,对应 TS 里的类型注解。 - 原始卡成员
\text{string}、\text{number}、\text{boolean}是三种最常见的原始类型。 - 特殊卡成员
\text{any}是“开挂卡”,\text{unknown}是“待定卡”,两者作用完全不同。
联合类型
现实里,一扇门往往不认单一卡种:“访客走 1 号闸机,员工走 2 号闸机,但临时工没有专属卡怎么办?”门禁系统想了个办法:发一张“跨区双功能卡”,上面写着 string | number,表示只要持有其中任意一种身份就能通行。
联合类型用竖线 | 把多个类型连接起来,让变量拥有“或”的关系。它特别适合表达“这个字段可能是数字 ID,也可能是字符串 ID”之类的真实场景。你还可以联合 null 模拟可空值,或者联合固定的字面量字符串,实现类似枚举的选项。
一个联合类型在数学上就是多个集合的并集:
联合类型给你提供了弹性,但门禁依然不会让你随便进——数据进入函数后,保安还得分清它到底是哪种身份,这就轮到类型守卫登场了。
| 符号读作“或者”,它把类型选择权交给了数据的实际值,而不是预先钉死。- 左边集合
\text{string} \cup \text{number}表示“字符串或数字的并集”,值可以是其中任意一个集合的成员。 - 结果写法
\text{string} \mid \text{number}是 TypeScript 中联合类型的实际语法。 - 等式含义两者等价,说明 TS 的类型操作符
|本质上就是集合的并集。
类型推断
老保安有一种本领:看到你胸口的工牌颜色,不用你开口,他就能判断你的身份。TS 也这样——你写 let name = "Alice",它自动推断出 name 的类型是 string,根本不需要你画蛇添足地写 : string。
推断规则很有讲究:let 声明的变量会推断为宽泛的基础类型,比如 let x = 1 推断为 number;而 const 声明因为不可变,会推断为更精确的字面量类型,比如 const c = "a" 推断为 "a"。函数的返回值、泛型参数,也能根据表达式自动推断。
推断的过程简洁而强大:
类型推断让门禁系统变得贴心:大部分常见情况不用你手动贴标签,保安看初值就知道你是谁。但要注意,当 TS 推不出准确类型时,它会“偷懒”成 any——这时候你最好主动补上类型,别让门禁形同虚设。
- 变量声明
\text{let } x = 1是 JavaScript 的普通写法,TS 会读取这个初值。 - 推理箭头
\Longrightarrow表示“由此推导出结论”。 - 结论标注
x : \text{number}表示 x 被自动判定为 number 类型。
类型守卫
拿到联合类型的数据后,保安不能凭一张“双功能卡”就放行。他会先问:“你到底是员工还是访客?” 然后根据回答,把你引导到对应的闸机。这个“根据类型选择分支”的过程,就叫类型守卫。
TS 最常用的守卫工具是 typeof,它可以精确判断 string、number、boolean 等原始类型。instanceof 适合判断类实例,in 适合判断对象是否拥有某个属性。每做完一次判断,TS 就会在分支内“收窄”变量的类型,让它从 string | number 变成单一的 string 或 number。
收窄的逻辑就像条件判断的推理:
有了守卫,联合类型不再是“薛定谔的身份”,而是“过闸机后明确身份”。记住:收窄越细致,你的代码就越不容易把字符串当数字去乘、把数字当字符串去拼接。
typeof 检查命中时,TS 会在当前分支内把 v 的类型收缩为 string,这就是类型收窄。- 守卫条件
\text{typeof } v = \text{'string'}是运行时判断,检查变量 v 是否为字符串。 - 推理结果
\Longrightarrow表示满足条件后推导出的新结论。 - 收窄类型
v : \text{string}表明在这个分支里 v 被安全地当作 string 使用。
工程配置与迁移
门禁系统再好,也得有制度支撑。tsconfig.json 就是整栋楼的“安保制度文件”,它决定了哪些门禁规则必须开启。其中最要紧的是 strict 模式——开启后,TS 会拒绝所有隐式的 any,并把 null 和 undefined 都纳入检查,让安全级别瞬间拉满。
对于已经运行多年的老 JS 项目,你不可能一夜之间给所有人都补办卡片。聪明做法是“渐进式迁移”:先开启 allowJs 让 TS 项目能混编 JS 文件,再用 checkJs 逐步启用检查,最后对最关键的模块优先收编。就像先给重要楼层装门禁,再慢慢推广到全楼。
严格模式可以理解为多条规则的汇总:
别把 strict 当成负担,它是门禁的“最高权限设置”。老项目迁移也一样,不要急着推倒重建——先把门禁装上,再逐步收紧,才能让团队在风平浪静中完成过渡。
- 严格模式
\text{strict}是 tsconfig 中的总开关,开启表示采用最高安全配置。 - 逻辑与
\land表示“并且”,说明这些规则必须同时满足。 - 关键子规则
\text{noImplicitAny}禁止隐式 any,\text{strictNullChecks}强制处理空值。
AI 应用场景
前端团队代码巡检
阶段:数据标准化与代码质量保障
解决的问题:在多模块协作中,接口返回的数据常被随意使用,导致取值错误。TS 的类型标注相当于给每个接口数据提前盖好“通行证章”,AI 分析的字段类型与真实数据一致,减少线上事故。
具体案例:某团队在接入用户画像 API 后,用 type User = { name: string; age?: number; tags: string[] } 定义数据结构。前端在消费数据时,若误读 age 为字符串,编译期就会报错,而不是等运行后页面白屏。
旧项目渐进式迁移
阶段:技术债清理与系统重构
解决的问题:存量 JS 代码直接迁移 TS 风险极高,容易引发大面积回归。采用 allowJs + checkJs 的分步策略,像先给老楼装上部分门禁再逐步扩容,让迁移过程平滑可控。
具体案例:一个运营后台系统有 200 多个 JS 文件,团队先开启 allowJs 混编,再按业务模块逐个增加类型声明,最后开启 strict。三个月内完成迁移,线上故障率下降 40%。
数据模型的联合处理
阶段:多源数据接入与规范化
解决的问题:多个数据源返回的 ID 可能是字符串也可能是数字,用联合类型 string | number 统一建模,配合 typeof 守卫分支处理,避免不必要的字符串转换。
具体案例:在 IoT 平台采集设备数据时,deviceId 有的来源是 "sensor-01",有的是 1001。统一收窄后分别走不同匹配逻辑,系统稳定性显著提升。
Python 演示
def simulate_type_narrowing(value): if isinstance(value, str): return "string 通道: " + value.upper() if isinstance(value, int): return "number 通道: " + str(value * 2) if value is None: return "null 通道: 检查失败" return "unknown: 需要更多守卫" def main(): samples = [42, "hello", None, 3.14] for s in samples: print(simulate_type_narrowing(s)) if __name__ == "__main__": main()
typeof 类型守卫与收窄的过程,展示不同数据类型进入分支后如何被分类处理。def simulate_type_narrowing(value):模拟一个接受联合类型参数的处理函数,对应 TS 的function process(value: string | number | null)。if isinstance(value, str):等价于if (typeof value === "string"),判断数据是否为字符串。return "string 通道: " + value.upper()在 str 分支内安全调用字符串方法,对应 TS 收窄后的类型安全操作。if isinstance(value, int):对应typeof value === "number"的守卫条件。return "unknown: 需要更多守卫"对应 TS 中unknown类型:未经过守卫确认前,不能贸然使用。
常见误区
误区我以为 TypeScript 会在代码运行时帮我拦截类型错误,像代码报错后还能提示“这里类型不对”。
正解TS 的类型检查发生在编译期,也就是代码运行之前。一旦编译通过,生成的 JavaScript 中就没有类型信息了。它像大楼门口的安检,进门之后保安不会再跟着你;运行时错误依然需要逻辑来兜底。
为何常见:因为我们在编辑器里看到了红色波浪线,误以为是“运行时保护”,实际那是编译器的静态提示。
误区遇到不确定的类型,我直接写成 `any`,省得思考,反正也能编译通过。
正解any 等于关闭了这道数据的门禁,任何人都能混进来,类型系统瞬间失效。如果你整天用 any,那 TS 的价值基本归零。应该优先用 unknown 配合类型守卫,或者用联合类型精准表达可能情况。
为何常见:any 写起来最快、报错最少,容易让人产生“类型检查很麻烦”的错觉。
误区既然 TS 能自动推断所有类型,那我干脆不写了,让编译器猜。
正解类型推断确实能处理大量初值、返回值场景,但函数参数、复杂接口、跨模块边界等位置,TS 无法获得足够上下文,往往会退化成 any。门禁系统也需要你手动设置关键卡口,否则它不知道访客该去哪层。
为何常见:刚上手时发现 let x = 1 自动就是 number,误以为推断万能。
误区声明了 `let x: string | number` 之后,我以为在代码里可以直接把 x 当字符串用,毕竟我已经说了它可能是字符串。
正解TS 不会自动帮你在分支里确定类型,必须通过 typeof、instanceof、in 等守卫主动收窄,才能安全使用具体类型的属性或方法。否则,TS 会在你调用 .toUpperCase() 时提示 number 类型不存在该方法。
为何常见:人看到“联合”会理解成“都包含”,而 TS 更严格:未收窄前,能用的方法必须是所有成员共有的。
误区既然要用 TS,就应该把所有 .js 文件一次性改成 .ts,否则感觉不彻底。
正解大型老项目强行全量迁移会导致巨大的回归风险和团队阻力。推荐渐进式策略:先开启 allowJs 混编、再开启 checkJs、然后按模块逐个改为 .ts 并逐步收紧 strict。门禁不必一天装好,慢慢扩大覆盖范围更稳妥。
为何常见:看到官方文档的“快速迁移”例子,误以为所有项目都能在短时间内完成转换。
面试题
Q1. TypeScript 相比 JavaScript 最大的优势是什么?
- A.运行速度更快
- B.编译期类型检查← 正确答案
- C.无需编译直接运行
- D.浏览器原生支持
答案:B。TS 的核心价值是在编译期做静态类型检查,让类型错误在运行前被揪出来。运行速度、原生支持与 JS 并无区别,“无需编译”恰恰相反,TS 必须编译成 JS 才能运行。
Q2. 下列哪一项是 TypeScript 中的原始类型?
- A.object
- B.string← 正确答案
- C.array
- D.function
答案:B。string、number、boolean 等都是原始类型,而 object、array、function 都属于对象类型。题目问“原始类型”,所以选 string。
Q3. let x: string | number 表示什么含义?
- A.x 同时是 string 和 number
- B.x 可以是两者中的任意一个← 正确答案
- C.x 会自动在两种类型间切换
- D.x 只能被赋值为 any
答案:B。联合类型用 | 表示“或”,x 在赋值时只能是 string 或 number 之一,而不是同时拥有两个身份。类型也不会自动切换,更不要求 any。
Q4. 关于 TypeScript 类型推断,下列说法正确的有哪些?
- A.
let n = 1推断为 number← 正确答案 - B.
const c = "a"推断为字面量类型← 正确答案 - C.函数返回值可以根据逻辑推断← 正确答案
- D.any 总是能被自动推断为具体类型
答案:ABC。A、B、C 都是常见的推断行为。D 错误:any 本身代表放弃检查,不会自动变成更具体的类型,反而会“污染”后续推断。
Q5. 下列哪个类型用 typeof 做守卫时最安全可靠?
- A.string← 正确答案
- B.object
- C.array
- D.null
答案:A。typeof x === "string" 能准确判断所有字符串。而 typeof [] === "object",无法区分数组和普通对象;typeof null === "object" 更是经典坑。所以 string 最可靠。
Q6. 哪些运算符可以在 TypeScript 中用来实现类型收窄?
- A.typeof 判断原始类型← 正确答案
- B.in 判断对象属性← 正确答案
- C.instanceof 判断类实例← 正确答案
- D.数组索引直接收窄
答案:ABC。typeof、in、instanceof 都是 TS 官方认可的类型守卫写法。数组索引本身不是类型守卫,无法让 TS 自动收窄,必须配合守卫表达式使用。
Q7. tsconfig.json 中开启“严格模式”的关键选项是?
- A.strict← 正确答案
- B.outDir
- C.moduleFormat
- D.syntaxLevel
答案:A。strict 是总开关,开启后同时启用 noImplicitAny、strictNullChecks 等严格规则。outDir 是输出目录,moduleFormat 并非标准选项,所以选 strict。
Q8. 关于 any 和 unknown,以下说法正确的是?
- A.any 会关闭该变量的类型检查← 正确答案
- B.unknown 必须先收窄才能赋值给具体类型← 正确答案
- C.any 可以把值赋给任何类型的变量← 正确答案
- D.unknown 可以像 any 一样直接调用任意方法
答案:ABC。any 完全跳过检查,可赋给任意类型;unknown 是“待定身份”,必须通过守卫收窄后才能安全使用。D 错误,unknown 不能直接调用方法,否则违背了它“待验证”的本意。
Q9. 将老 JS 项目迁移到 TS 时,最推荐的策略是?
- A.一次性把全部文件改为 .ts
- B.开启 allowJs 再逐步收紧检查← 正确答案
- C.放弃类型检查只保留后缀
- D.所有变量都改成 any 再改回来
答案:B。渐进式迁移风险最小:先 allowJs 混编,再 checkJs,再按模块收编。一步到位容易引发大规模回归;用 any 过渡虽然常见,但必须配合逐步收窄,不能作为最终策略。
Q10. 类型守卫让联合类型在分支内变成具体类型的过程叫什么?
- A.类型收窄← 正确答案
- B.类型扩张
- C.类型合并
- D.类型覆盖
答案:A。从 string | number 收窄到 string,如同机场安检把“员工卡或访客卡”最终确认为员工卡,这个精确化的过程叫类型收窄。其余三个术语都不是 TS 的标准概念。