## 为什么重视类型收窄
类型收窄是将更宽泛的联合类型在控制流中缩小为更具体类型的过程。合理收窄可消除大量运行期分支错误,配合“可辨识联合”与穷尽性检查,能把逻辑漏洞前置到编译期。
## 核心方法与示例
### 基础收窄:`typeof` / `instanceof` / `in`
function format(v: string | number | Date) {
if (typeof v === "string") return v.trim();
if (typeof v === "number") return v.toFixed(2);
if (v instanceof Date) return v.toISOString();
}
type User = { name: string; email?: string };
function hasEmail(u: User) {
if ("email" in u && u.email) {
return u.email.toLowerCase();
}
return null;
}
### 可辨识联合(Discriminated Union)
通过“标签字段”让联合在控制流中自动收窄。
type Shape =
| { kind: "circle"; r: number }
| { kind: "rect"; w: number; h: number };
function area(s: Shape) {
switch (s.kind) {
case "circle":
return Math.PI * s.r ** 2;
case "rect":
return s.w * s.h;
default:
const _exhaustive: never = s; // 保证穷尽
return _exhaustive;
}
}
### 穷尽性检查(Exhaustiveness Check)与 `never`
在 `switch` 或模式分支中添加一个 `default` 分支,将未覆盖的情形强制为编译错误。
type Status = "idle" | "loading" | "success" | "error";
function render(status: Status) {
switch (status) {
case "idle":
return "…";
case "loading":
return "Loading";
case "success":
return "OK";
case "error":
return "Fail";
default:
const _: never = status; // 新增枚举时,这里会报错提醒补齐
return _;
}
}
### 字面量与只读:`as const`
用 `as const` 锁定字面量与只读属性,让联合更可辨识、收窄更稳定。
const actions = [
{ type: "add", payload: 1 },
{ type: "remove", payload: 2 },
] as const;
type Action = typeof actions[number]; // { type: "add" | "remove"; payload: number }
### 更安全的匹配:`satisfies`(TS ≥ 4.9)
让值在不改变推断结果的同时满足目标结构约束,防止遗漏字段或错误类型。
type RouteMap = Record<string, { path: string; secure: boolean }>;
const routes = {
home: { path: "/", secure: false },
profile: { path: "/u/:id", secure: true },
} satisfies RouteMap; // 若字段缺失或类型不符会报错
// 推断保持原状:routes.profile.path 是 string(非硬编码字面量)
## tsconfig 配置建议(与收窄相关)
- `strict: true`:开启整体严格检查,使控制流分析更可靠。
- `noUncheckedIndexedAccess: true`:索引访问自动携带 `undefined`,促使存在性检查。
- `exactOptionalPropertyTypes: true`:让可选属性的读写语义更精确。
- `noFallthroughCasesInSwitch: true`:禁止 `switch` 落空;与穷尽性检查配合更安全。
示例片段:
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noFallthroughCasesInSwitch": true
}
}
## 工程落地清单
- 为所有联合类型添加“标签字段”,统一命名如 `kind`/`type`。
- 在关键分支使用 `switch + never` 做穷尽性检查,配合评审强制执行。
- 对常量集合使用 `as const`,避免字面量退化为宽类型。
- 在配置/字典型对象上使用 `satisfies` 校验结构正确性。
- 引入 ESLint 规则(如 `default-case-last`),减少分支陷阱;CI 运行 `tsc --noEmit`。
## 常见问题与解法
- 分支遗漏:以 `never` 校验兜底;新增枚举时编译器会提醒。
- 可选属性误用:开启 `exactOptionalPropertyTypes` 并做显式存在性检查。
- 宽类型污染:用 `as const` 固化、或通过工厂函数返回更窄类型。
## 总结
通过类型收窄与模式匹配的系统化实践,可以把分支缺陷前置到编译期、提升重构信心与可维护性。配合严格的 tsconfig 与工程规范,TypeScript 将成为可靠的“静态防线”。

发表评论 取消回复