TypeScript 04:类型断言与类型守卫
有 JS 基础即可阅读。建议先读第 01~03 课(本课大量用到第 02 课的「收窄」)。
1. 问题的起点:信息差
TS 只认代码里写出来的东西,而你经常「额外知道」一些它不知道的事(接口文档、页面结构、业务规则):
ts
const raw = '{"name":"Tom","age":18}'
const parsed = JSON.parse(raw)
// TS 的视角:什么都可能返回,不敢保证任何字段
// 你的视角:看过文档,确定就是 { name: string; age: number }
TS 的原则是只认证据,不信口头保证。本课全部内容,就是「额外知识」交给它的几种方式。
2. as:签担保书
把你的知识硬塞给 TS:
ts
const assured = JSON.parse(raw) as { name: string; age: number }
// 读法:我看过数据了,它就是这个形状,你按这个来
两个本质:
- 换标签,不换值------运行时什么都没做,值原封不动(只是改了快递箱上的标签,箱里东西没变)
- 担保书 ≠ 验证器------TS 签收后不再核对,谎报了它不管:
ts
const risky = JSON.parse('{"name":"Tom"}') as { name: string; age: number }
risky.age // ✅ 编译通过 → ❌ 运行时 undefined
两个类型毫无重叠时 TS 会拒绝担保:Conversion of type 'X' to type 'Y' may be a mistake(强行硬转要 as unknown as T,等于放弃检查,慎用)。
3. !:快捷担保------只担保「非空」
ts
const btn = document.querySelector('.submit-btn')! // 我确定页面上一定有
btn.addEventListener('click', onClick)
担保错了就是运行时 TypeError。更稳的替代是给 TS 看证据:if (btn) { ... }。
4. is:把「证据」打包成函数
第 02 课的收窄(typeof / in)有效,是因为 TS 亲眼看到了证据 ------但证据写在 if 里不可复用。is 把证据装进函数,随处复用、收窄照常生效:
ts
type Bird = { fly: () => string }
type Fish = { swim: () => string }
// 读法:函数返回 true 时,调用处的 pet 就是 Fish
function isFish(pet: Bird | Fish): pet is Fish {
return 'swim' in pet // 函数体里还是那个证据
}
function feed(pet: Bird | Fish) {
if (isFish(pet)) {
return pet.swim() // ✅ 收窄生效
}
return pet.fly() // ✅ 剩余分支是 Bird
}
关键对比------返回类型写成 boolean 链条就断了:
ts
function isFish(pet: Bird | Fish): boolean // ❌ TS 不知道 true 和 Fish 有什么关系
function isFish(pet: Bird | Fish): pet is Fish // ✅ true 就是证据
5. never 与穷尽检查:防盗警报
never = 没有任何值能是它(「不可能存在」)。推演一遍穷尽检查:
ts
type Shape = { kind: 'circle'; r: number } | { kind: 'rect'; w: number; h: number }
function area(s: Shape): number {
switch (s.kind) {
case 'circle':
return Math.PI * s.r ** 2 // 这里 s 收窄为 circle 分支
case 'rect':
return s.w * s.h // 这里 s 收窄为 rect 分支
default:
const _alarm: never = s // 能掉进 default 的 s = 谁都没处理 = 空集 = never
return _alarm
}
}
这行能编译通过 = TS 替你确认了「全覆盖」。以后给 Shape 加 { kind: 'square' } 却忘写 case:default 里的 s 真的可能是 square (不再是空集),赋给 never 立刻报错------平时不响,一响必有贼(漏分支)。
6. satisfies:as 的改良版(初学可先跳过)
同样贴标签,但贴之前先验一遍形状 ,且不丢精确推断:
ts
const config = {
home: { path: '/', title: '首页' },
about: { path: '/about', title: '关于' },
} satisfies Record<string, { path: string; title: string }>
config.home.path // ✅ 精确类型还在,能补全
// ❌ 某个 title 拼成 tittle → 立刻报错
配置表、路由表、常量表这类「结构要验、细节要留」的场景优先 satisfies。
7. 断言 vs 守卫怎么选
| 手段 | 语义 | 用在哪 |
|---|---|---|
as / ! |
我担保(TS 不再验证) | 边界处:第三方库缺类型、DOM 查询、已知结构的 JSON |
is / typeof / in |
先验证再用(证据) | 项目内部逻辑,默认选它 |
satisfies |
验证但保留精确推断 | 配置 / 路由 / 常量表 |
原则:边界可以断言(守好门),内部优先收窄。断言用得越多,类型系统越像摆设。
8. 高频报错速查
| 报错 | 人话 |
|---|---|
Conversion of type 'X' to type 'Y' may be a mistake |
两个类型没重叠,TS 拒绝瞎担保 |
Type 'Square' is not assignable to type 'never' |
穷尽检查触发:switch 漏了新分支 |
Object is possibly 'undefined' |
该收窄或 ! 了 |
Property 'x' does not exist on type 'unknown' |
unknown 未收窄就想用 |