返回笔记列表
01 · NOTES / WebGIS 开发

第 37 节 · 类型系统全解

2026 年 9 月 1 日

第 37 节 · 类型系统全解

📌 版本信息:基于 TypeScript 5.x(2026-08-29 核对) 📚 来源:TS 手册 · 类型收窄TS 手册 · 更多 on 函数阮一峰 TS 教程

一、这一节的目标(TS 阶段二)

  1. 精通类型收窄(narrowing)的全部手段与判别式联合
  2. 掌握 unknown 的安全使用模式(外部数据守门)
  3. 精通函数类型细节:重载、void 特殊性、never
  4. 理解类型断言的三种形式与使用边界
  5. 建立"类型先行"的建模工作流

二、类型收窄全手段

type Shape = Circle | Rect;                    // 判别式联合的成员
interface Circle { kind: 'circle'; r: number }
interface Rect   { kind: 'rect';  w: number; h: number }

// ① 判别式联合(discriminated union):共用一个"标签字段"
function area(s: Shape): number {
  if (s.kind === 'circle') return Math.PI * s.r ** 2;   // 分支内 s 收窄为 Circle
  return s.w * s.h;                                     // 剩余分支收窄为 Rect
}
// ⚠️ GIS 数据建模首选模式:GeoJSON 的 Point/LineString/Polygon 就是判别式联合
//   (type 字段正是它的"判别式"!)

// ② typeof 收窄(原始类型)
// ③ instanceof 收窄(类实例):err instanceof ApiError
// ④ in 收窄(字段存在性):'mag' in props
// ⑤ 真值收窄:if (el) 排除 null/undefined

穷尽检查(exhaustiveness):switch 覆盖所有联合成员后,default 分支赋 never——将来加新成员没处理时编译期就报错

function area(s: Shape): number {
  switch (s.kind) {
    case 'circle': return Math.PI * s.r ** 2;
    case 'rect':   return s.w * s.h;
    default:
      const _exhaustive: never = s;   // 加了 'triangle' 却忘了改这里?此处立即报错
      return _exhaustive;
  }
}

三、unknown:外部数据的守门模式

// fetch 的 json() 返回 any?不:现代 lib.dom 标注为 unknown——逼你验证
const data: unknown = JSON.parse(raw);

// 守门函数:验证 + 收窄(运行时校验 + 编译期类型的交接点)
function isQuakeCollection(x: unknown): x is QuakeCollection {
  return (
    typeof x === 'object' && x !== null &&
    'type' in x && (x as { type: unknown }).type === 'FeatureCollection' &&
    'features' in x && Array.isArray((x as { features: unknown }).features)
  );
}
// `x is QuakeCollection` 叫"类型谓词"(type predicate):
// 返回 true 时,调用方作用域里 x 自动收窄为 QuakeCollection
if (isQuakeCollection(data)) {
  data.features;   // ✅ 这里已是 QuakeCollection
}
// 认识进阶词汇:Zod 等库用"Schema 推导类型"把这套守门自动化(第 13 模块 Pydantic 的 TS 表亲)

四、函数类型深水区

// 函数重载:同一个函数、多套"入参形状 → 返回形状"的合同
function parseCoord(input: string): [number, number];
function parseCoord(input: [number, number]): [number, number];
function parseCoord(input: string | [number, number]): [number, number] {
  return typeof input === 'string' ? input.split(',').map(Number) as [number, number] : input;
}
// 重载签名给调用方看,最后一条实现签名只有编译器看

// void 的特殊规则:void 返回类型的函数位上,可以塞"有返回值"的函数(返回值被忽略)
const callbacks: (() => void)[] = [];
callbacks.push(() => 123);   // ✅ 合法——JS 里忽略返回值很常见

// never:never 类型的变量不能被赋任何值;它常出现在"穷尽检查"与"无限错误"

五、类型断言:三种形式与边界

// ① as 断言:"我比编译器更懂"——把宽/未知收窄到具体
const el = document.querySelector('#map') as HTMLDivElement;

// ② 非空断言 !:"我保证不是 null/undefined"
el!.innerHTML = 'ok';

// ③ 双重断言 as unknown as T:跨大类型强转(几乎总是坏味道)

// 边界纪律:
// - 断言不做任何运行时转换,只改"编译器的看法"——错了照样炸
// - 数据边界(fetch/外部输入)用守门函数(is 谓词),不用 as
// - as 只用于"你确知而编译器不知道"的窄场景(DOM 查询、类型谓词内部)

六、自测题

  1. 判别式联合的"判别式"是什么?GeoJSON 怎么体现?
  2. 穷尽检查怎么写?防的是什么?
  3. unknown 与 any 在"使用前"的要求差在哪?
  4. 类型谓词 x is T 给调用方带来什么?
  5. as 断言会改变运行时行为吗?数据边界为什么不用 as?

参考答案

  1. 所有成员共有的字面量字段(如 kind/type);switch 它即可精确收窄——GeoJSON 的 type: 'Point' | 'LineString' | 'Polygon' 就是。
  2. default 分支把收窄结果赋给 never 类型变量;防止将来扩展联合成员时漏处理分支。
  3. any 直接随便用;unknown 任何操作前必须先收窄/验证。
  4. 返回 true 时该变量在调用方作用域自动收窄为 T,无需再断言。
  5. 不会(纯编译期标注);as 不会做任何校验,边界数据用 as 等于裸奔,要用守门函数(is 谓词 / Schema 库)。

七、下一步

类型系统精通 → 第 38 节:工具类型全解,Partial/Pick/Record…逐个讲透。

TAGSweb