TypeScript 04:类型断言与类型守卫

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 未收窄就想用
相关推荐
guzicheng19901 小时前
webpack 安装笔记:全局 vs 本地,以及一个 PATH 环境变量的坑
前端·webpack
西索斯coding1 小时前
Claude Code 工具调用返回空怎么办?claude-opus-5.5 的 input_schema 校验踩坑记录与修复方案
服务器·前端·javascript·ai
疯狂敲代码的老刘1 小时前
MarvellousSuspender浏览器扩展:1.4K星开源标签页冻结神器,Chrome吃内存终结者的干净复活版
前端·chrome
码上上班1 小时前
shell 脚本知识点总结
前端·chrome
程序员Sunday1 小时前
Flex 和 Grid 有什么区别?一维布局、二维布局怎么选?
前端·css·面试
geovindu1 小时前
rust: pool Pattern
开发语言·后端·设计模式·rust·对象池简写模式
Jlzn88881 小时前
华南PACK线自动化率怎么统计才靠谱
前端·javascript·自动化
濮水大叔1 小时前
Cabloy/Vona 如何避开 NestJS 的循环依赖问题:懒属性注入机制源码解析
typescript·node.js·nestjs
拖孩1 小时前
AI 时代,个人开发者到底能不能靠一个小程序赚到钱
前端·后端·微信小程序