TypeScript 可辨识联合:让状态分支和数据绑定

背景 请求成功时才有 data,失败时才有 error。这种关联如果只靠可选字段表达,调用方很容易忘记判定状态。 核心判断 每个分支使用唯一字面量作为判别字段。 switch 分支内使用对应数据,让编译器完成收窄。 用 never 做穷尽检查,新增状态时迫使消费者同步处理。 最小示例 下面保留核心逻辑;业务类型、调用入口和环境配置按项目补齐。 type Result<T> = { status: "loading" } | { status: "ok"; data: T } | { status: "failed"; error: Error }; 边界与误区 可辨识联合只约束编译期代码,不能证明 HTTP 返回的 JSON 符合声明。不要在最后用类型断言绕过穷尽检查。 验证方式 增加一个 cancelled 状态,确认遗漏的消费者编译失败;边界输入单独做运行时校验,测试不合法状态与缺失字段。 小结 为每种状态定义唯一字面量字段,让编译器阻止 loading、error、data 的错误组合。

2026年9月10日 · 1 分钟 · BvBeJ

TypeScript 类型体操工程化:从泛型约束到模板字面量

背景与问题界定 TypeScript 的类型系统是图灵完备的,这意味着理论上可以用类型表达任意复杂的约束条件。然而,类型体操长期被视为"面试炫技"或"开源库的奢侈品",在实际业务项目中鲜有深度应用。常见的痛点包括:API 响应类型与前端请求参数之间的映射冗余、事件总线在编译期无法约束 payload 类型、数据库查询构建器缺乏字段级别的类型安全。这些问题本质上是类型系统能力与实际工程收益之间的断层——不是做不到,而是缺乏将其工程化的方法和习惯。我们需要一套方法论,将高级类型技巧转化为可复用的、文档良好的类型工具链。 目标拆解与工程约束 类型工具可测试:通过 tsd 或 expect-type 编写类型级别的单元测试,确保每次类型重构都不会破坏已有的约束。 优先使用泛型约束而非 as 断言:任何 as unknown as SomeType 的写法必须附带 RFC 说明为什么无法用纯类型表达,禁止在业务代码中滥用类型强制。 模板字面量类型优先于字符串枚举:当类型与固定字符串模板绑定(如 GET /api/users/:id),应使用模板字面量类型自动推导路径参数。 类型复杂度上限控制:单个类型表达式的嵌套深度不超过 5 层,超过时分解为中间类型别名,并用 JSDoc 标注计算意图。 方案设计 我们将类型工具分为三个层次:基础类型工具(Base Utilities)、业务类型模板(Business Templates) 和 外部类型适配器(External Adapters)。 基础类型工具对应于 lib/es5.d.ts 中缺失但日常高频使用的能力。例如 DeepPartial、RequiredByKeys、UnionToIntersection。这些工具使用条件类型、映射类型和 infer 实现,每个工具附带一个使用场景说明和类型测试: // 提取路由参数类型模板 type ExtractRouteParams<T extends string> = T extends `${string}:${infer Param}/${infer Rest}` ? { [K in Param | keyof ExtractRouteParams<Rest>]: string } : T extends `${string}:${infer Param}` ? { [K in Param]: string } : {} // 使用示例 type Params = ExtractRouteParams<'GET /api/users/:id/posts/:postId'> // 推导结果: { id: string; postId: string } 业务类型模板则针对实际业务场景。比如一个典型的列表查询请求,其 orderBy 字段应该只能来自接口返回字段的并集。通过 keyof + 模板字面量,我们可以实现: ...

2026年8月3日 · 1 分钟 · BvBeJ