Damnatiox
DOCUMENT / published

联合、交叉、控制流收窄与穷尽检查

联合、交叉、控制流收窄与穷尽检查 联合类型表达“多个可能之一”。可靠代码不靠断言选择分支,而是通过运行时证据让控制流分析逐步缩窄。 1. 常见收窄手段 TypeScript 理解 typeof 、 instanceof 、相等比较、真值判断、 in 、赋值、提前返回和部分标准库谓词。 typeof null 陷阱 JavaScript 中 typeof null === "object" ,因此对象检查必须排除 null : 2. 用户

TypeScript语言基础 2026/8/266 分钟阅读
# 语言基础# TypeScript# TypeScript语言基础

联合、交叉、控制流收窄与穷尽检查

联合类型表达“多个可能之一”。可靠代码不靠断言选择分支,而是通过运行时证据让控制流分析逐步缩窄。

1. 常见收窄手段

TypeScript
function describe(value: string | number | Date): string { if (typeof value === "string") return value.toUpperCase() if (value instanceof Date) return value.toISOString() return value.toFixed(2) }

TypeScript 理解 typeofinstanceof、相等比较、真值判断、in、赋值、提前返回和部分标准库谓词。

typeof null 陷阱

JavaScript 中 typeof null === "object",因此对象检查必须排除 null

TypeScript
function isRecord(value: unknown): value is Record<string, unknown> { return typeof value === "object" && value !== null }

2. 用户定义类型谓词

TypeScript
type Fish = { swim(): void } type Bird = { fly(): void } function isFish(animal: Fish | Bird): animal is Fish { return "swim" in animal } function move(animal: Fish | Bird): void { if (isFish(animal)) animal.swim() else animal.fly() }

谓词签名是对实现正确性的承诺;若实现返回错误结果,检查器会被误导。复杂数据应使用经过测试的 schema 解析器,而非只检查一个属性。

3. 断言函数

TypeScript
function assertString(value: unknown): asserts value is string { if (typeof value !== "string") { throw new TypeError("Expected string") } } function normalize(value: unknown): string { assertString(value) return value.trim() }

断言函数失败时必须中断正常控制流;若仅记录日志却继续返回,会破坏类型承诺。

4. 可辨识联合:状态机首选模型

TypeScript
type RequestState<T> = | { status: "idle" } | { status: "loading"; startedAt: number } | { status: "success"; data: T } | { status: "failure"; error: Error } function render<T>(state: RequestState<T>): string { switch (state.status) { case "idle": return "尚未开始" case "loading": return `加载中:${state.startedAt}` case "success": return JSON.stringify(state.data) case "failure": return state.error.message default: return assertNever(state) } } function assertNever(value: never): never { throw new Error(`Unhandled state: ${JSON.stringify(value)}`) }

新增状态而未更新 switch 时,state 不再是 never,编译器会在默认分支报告错误。

5. 交叉类型与分布

TypeScript
type WithId = { id: string } type WithVersion = { version: number } type Entity = WithId & WithVersion

联合是集合并集,交叉是同时满足约束。条件类型遇到裸类型参数时会对联合分布,后续高级类型章节会详细说明。

6. 收窄会在哪些地方失效

TypeScript
type Box = { value?: string } function print(box: Box): void { if (box.value !== undefined) { const snapshot = box.value queueMicrotask(() => { console.log(snapshot.toUpperCase()) }) } }

可变对象属性可能在回调执行前改变。保存已收窄的不可变局部值,通常比依赖跨闭包属性收窄更稳定。

7. in 与可选属性

若某联合成员的属性是可选的,它可能同时出现在 in 判断的两个分支中;不要假设 "swim" in value 的 false 分支一定排除了含可选 swim 的类型。

8. 边界解析模式

TypeScript
type Message = | { type: "text"; body: string } | { type: "count"; value: number } function parseMessage(input: unknown): Message { if (!isRecord(input) || typeof input.type !== "string") { throw new TypeError("Invalid message") } if (input.type === "text" && typeof input.body === "string") { return { type: "text", body: input.body } } if (input.type === "count" && typeof input.value === "number") { return { type: "count", value: input.value } } throw new TypeError("Unsupported message") }

解析器返回新的、已验证对象,比把原输入整体断言成目标类型更清晰。

参考:NarrowingUnions and Intersections