TypeScript 04:类型断言与类型守卫
有 JS 基础即可阅读。建议先读第 01~03 课(本课大量用到第 02 课的「收窄」)。
1. 问题的起点:信息差
TS 只认代码里写出来的东西,而你经常「额外知道」一些它不知道的事(接口文档、页面结构、业务规则):
constraw='{"name":"Tom","age":18}'constparsed=JSON.parse(raw)// TS 的视角:什么都可能返回,不敢保证任何字段// 你的视角:看过文档,确定就是 { name: string; age: number }TS 的原则是只认证据,不信口头保证。本课全部内容,就是「额外知识」交给它的几种方式。
2. as:签担保书
把你的知识硬塞给 TS:
constassured=JSON.parse(raw)as{name:string;age:number}// 读法:我看过数据了,它就是这个形状,你按这个来两个本质:
- 换标签,不换值——运行时什么都没做,值原封不动(只是改了快递箱上的标签,箱里东西没变)
- 担保书 ≠ 验证器——TS 签收后不再核对,谎报了它不管:
constrisky=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.!:快捷担保——只担保「非空」
constbtn=document.querySelector('.submit-btn')!// 我确定页面上一定有btn.addEventListener('click',onClick)担保错了就是运行时TypeError。更稳的替代是给 TS 看证据:if (btn) { ... }。
4. is:把「证据」打包成函数
第 02 课的收窄(typeof/in)有效,是因为 TS亲眼看到了证据——但证据写在 if 里不可复用。is把证据装进函数,随处复用、收窄照常生效:
typeBird={fly:()=>string}typeFish={swim:()=>string}// 读法:函数返回 true 时,调用处的 pet 就是 FishfunctionisFish(pet:Bird|Fish):petisFish{return'swim'inpet// 函数体里还是那个证据}functionfeed(pet:Bird|Fish){if(isFish(pet)){returnpet.swim()// ✅ 收窄生效}returnpet.fly()// ✅ 剩余分支是 Bird}关键对比——返回类型写成boolean链条就断了:
functionisFish(pet:Bird|Fish):boolean// ❌ TS 不知道 true 和 Fish 有什么关系functionisFish(pet:Bird|Fish):petisFish// ✅ true 就是证据5. never 与穷尽检查:防盗警报
never= 没有任何值能是它(「不可能存在」)。推演一遍穷尽检查:
typeShape={kind:'circle';r:number}|{kind:'rect';w:number;h:number}functionarea(s:Shape):number{switch(s.kind){case'circle':returnMath.PI*s.r**2// 这里 s 收窄为 circle 分支case'rect':returns.w*s.h// 这里 s 收窄为 rect 分支default:const_alarm:never=s// 能掉进 default 的 s = 谁都没处理 = 空集 = neverreturn_alarm}}这行能编译通过 = TS 替你确认了「全覆盖」。以后给Shape加{ kind: 'square' }却忘写 case:default 里的 s真的可能是 square(不再是空集),赋给never立刻报错——平时不响,一响必有贼(漏分支)。
6. satisfies:as 的改良版(初学可先跳过)
同样贴标签,但贴之前先验一遍形状,且不丢精确推断:
constconfig={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 未收窄就想用 |