01 · NOTES / WebGIS 开发
第 37 节 · 类型系统全解
2026 年 9 月 1 日
第 37 节 · 类型系统全解
📌 版本信息:基于 TypeScript 5.x(2026-08-29 核对) 📚 来源:TS 手册 · 类型收窄 | TS 手册 · 更多 on 函数 | 阮一峰 TS 教程
一、这一节的目标(TS 阶段二)
- 精通类型收窄(narrowing)的全部手段与判别式联合
- 掌握 unknown 的安全使用模式(外部数据守门)
- 精通函数类型细节:重载、void 特殊性、never
- 理解类型断言的三种形式与使用边界
- 建立"类型先行"的建模工作流
二、类型收窄全手段
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 查询、类型谓词内部)
六、自测题
- 判别式联合的"判别式"是什么?GeoJSON 怎么体现?
- 穷尽检查怎么写?防的是什么?
- unknown 与 any 在"使用前"的要求差在哪?
- 类型谓词
x is T给调用方带来什么? as断言会改变运行时行为吗?数据边界为什么不用 as?
参考答案
- 所有成员共有的字面量字段(如 kind/type);switch 它即可精确收窄——GeoJSON 的 type: 'Point' | 'LineString' | 'Polygon' 就是。
- default 分支把收窄结果赋给 never 类型变量;防止将来扩展联合成员时漏处理分支。
- any 直接随便用;unknown 任何操作前必须先收窄/验证。
- 返回 true 时该变量在调用方作用域自动收窄为 T,无需再断言。
- 不会(纯编译期标注);as 不会做任何校验,边界数据用 as 等于裸奔,要用守门函数(is 谓词 / Schema 库)。
七、下一步
类型系统精通 → 第 38 节:工具类型全解,Partial/Pick/Record…逐个讲透。
TAGSweb