ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

The Concise TypeScript Book 精讲:判别联合(Discriminated Unions)的类型收窄实战

The Concise TypeScript Book 精讲:判别联合(Discriminated Unions)的类型收窄实战 文档教程【免费下载链接】typescript-bookThe Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.项目地址https://gitcode.com/gh_mirrors/typ/typescript-book点击查看免费下载本篇为开源仓库 The Concise TypeScript Book保加利亚语章节原文内容与英文版 discriminated-unions.md 一致的深度解析。判别联合Discriminated Unions是 TypeScript 中以一个公共字面量属性discriminant判别属性驱动联合类型收窄的核心模式广泛用于状态机、表单处理、协议解析、Redux Action 等场景。读完本文你将掌握判别联合的建模三要素、switch/条件语句中的自动收窄机制以及结合never类型实现穷尽性检查Exhaustiveness Checking的完整方案。一、什么是判别联合判别联合是联合类型Union Type的一种。普通联合类型表示一个值可以是若干类型中的任意一种用|符号连接各个成员类型例如string | number详见 union-type.mdlet x: string | number; x hello; // Valid x 123; // Valid而判别联合在此之上增加了一个关键约束联合的每个成员类型都共享一个公共属性——判别属性discriminant——且该属性的取值是唯一的字面量类型。TypeScript 编译器依靠这个字面量取值就能在控制流中精确地判断当前值到底是哪个成员从而自动收窄narrow该值的类型。一个标准的判别联合由三个要素构成公共判别属性每个成员都声明同一个属性惯例命名为kind、type、status、tag等其类型是字面量类型Literal Type。唯一字面量取值每个成员的判别属性值互不相同构成该成员的身份标识。联合组合用|将各成员类型组合为联合类型。字面量类型Literal Type是集合中的单元素集合只精确对应一个 JavaScript 原始值。字符串、数字、布尔字面量都可以充当判别值详见 literal-types.mdconst a a; // String literal type const b 1; // Numeric literal type const c true; // Boolean literal type二、核心示例用 kind 判别 Shape 联合原文档给出了判别联合最经典的几何图形示例。Square与Circle共享属性kind取值分别为字面量square与circle二者组成Shape联合type Square { kind: square; // Дискриминант判别属性 size: number; }; type Circle { kind: circle; // Дискриминант判别属性 radius: number; }; type Shape Square | Circle; const area (shape: Shape) { switch (shape.kind) { case square: return Math.pow(shape.size, 2); case circle: return Math.PI * Math.pow(shape.radius, 2); } }; const square: Square { kind: square, size: 5 }; const circle: Circle { kind: circle, radius: 2 }; console.log(area(square)); // 25 console.log(area(circle)); // 12.566370614359172这段代码揭示了判别联合的两大运行机制成员互斥的编译期保证在switch (shape.kind)的case square分支内编译器已经确认shape一定是Square因此可以直接访问shape.size而不会报错在case circle分支内则可以直接访问shape.radius。反过来如果试图在square分支访问shape.radius编译器会立即报错——因为该属性在Square上不存在。对象字面量结构一致性创建square、circle变量时对象必须同时包含判别属性与对应的数据属性缺一不可否则赋值时报类型错误。三、判别属性为什么能收窄收窄机制盘点判别联合的收窄本质上是 TypeScript 控制流分析Control Flow Analysis的产物。控制流分析会在条件分支中静态推导变量类型并自动收窄详见 control-flow-analysis.md 与 narrowing.md。与判别联合配合的常见收窄手段包括1. switch 语句 case 字面量匹配最常用如上述area示例所示。这是判别联合 穷尽性检查的标准搭配。2. if / else 相等性收窄判别联合也可以直接用相等运算符、!在if中收窄type Square { kind: square; size: number }; type Circle { kind: circle; radius: number }; type Shape Square | Circle; const describe (shape: Shape) { if (shape.kind square) { return Square with size ${shape.size}; } return Circle with radius ${shape.radius}; };这里if分支内shape被收窄为Squareelse分支由于判别属性只有两个取值被收窄为Circle。收窄的判定依据正是相等性收窄Equality narrowing比较变量是否等于某个特定字面量值来收窄类型。3. 三元表达式与布尔表达式中的收窄判别属性可以直接出现在三元判断或/||表达式中const isSquare (shape: Shape) shape.kind square ? size${shape.size} : radius${shape.radius};4.in操作符收窄非判别场景的补充当联合成员没有公共判别属性时可以退而使用in操作符按属性存在性收窄详见 narrowing.mdtype Dog { name: string; breed: string; }; type Cat { name: string; likesCream: boolean; }; const getAnimalType (pet: Dog | Cat) { if (breed in pet) { return dog; } else { return cat; } };对比可见判别联合要求成员共享同一属性名收窄依据是属性值in收窄不要求共享属性名收窄依据是属性是否存在。前者在分支较多、语义明确时更安全后者适合成员属性差异大的场景。四、深入原理TypeScript 4.4 起的间接判别收窄TypeScript 的收窄并不局限于直接对shape.kind做判断。从 TypeScript 4.4 开始控制流分析可以应用于通过const变量间接引用的判别属性访问与条件表达式。这意味着你可以把判别判断抽到命名常量中代码依然能正确收窄详见 control-flow-analysis.mdconst f2 ( obj: { kind: foo; foo: string } | { kind: bar; bar: number } ) { const isFoo obj.kind foo; if (isFoo) { obj.foo; // 收窄生效obj 被推断为 { kind: foo; foo: string } } else { obj.bar; // 收窄生效obj 被推断为 { kind: bar; bar: number } } };需要特别注意收窄失效的两种情况判别结果变量不是const如果let isString typeof x string因为isString可能被后续赋值改变编译器无法基于它收窄被收窄对象在函数体内被重新赋值obj obj这样的赋值会打断编译器对obj的跟踪导致基于isFoo的收窄失效。const f1 (x: unknown) { let isString typeof x string; if (isString) { x.length; // ErrorisString 非 const不触发收窄 } }; const f6 ( obj: { kind: foo; foo: string } | { kind: bar; bar: number } ) { const isFoo obj.kind foo; obj obj; if (isFoo) { obj.foo; // Errorobj 在函数体内被赋值收窄被打断 } };另外TypeScript 对条件表达式中的间接引用最多分析五层间接寻址。这一约束说明过度深层的间接判别判断例如把判别结果存到多层引用的属性中可能无法触发收窄实践中应保持判别判断直接、扁平。五、实战进阶与 never 类型配合的穷尽性检查判别联合真正的工程价值在于穷尽性检查Exhaustiveness Checking编译器保证联合的每一个成员都被处理。实现方式是在switch的default分支中把direction赋值给never类型变量详见 exhaustiveness-checking.mdtype Direction up | down; const move (direction: Direction) { switch (direction) { case up: console.log(Moving up); break; case down: console.log(Moving down); break; default: const exhaustiveCheck: never direction; console.log(exhaustiveCheck); // 这行永远不会被执行 } };工作原理never表示永不出现的值详见 never-type.md。当Direction的所有取值都被case覆盖时default分支中的direction被收窄为never赋值合法。而一旦有人给Direction增加新取值例如left却忘记添加对应的casedefault分支中direction的类型就变成left赋值给never会触发编译错误从而在编译期强制开发者补全处理逻辑。更实用的写法是在default分支中抛出一个携带该值的错误既保证穷尽性检查又能在运行时暴露未处理分支type Direction up | down; const move (direction: Direction): void { switch (direction) { case up: // 向上移动 break; case down: // 向下移动 break; default: const exhaustiveCheck: never direction; throw new Error(Unhandled direction: ${exhaustiveCheck}); } };把这两种模式判别联合 never穷尽性检查应用到开篇的Shape示例即可得到生产级的完整版本type Square { kind: square; size: number }; type Circle { kind: circle; radius: number }; // 未来新增成员只需在此追加编译器会强制你更新 area 与 describe type Shape Square | Circle; const area (shape: Shape): number { switch (shape.kind) { case square: return Math.pow(shape.size, 2); case circle: return Math.PI * Math.pow(shape.radius, 2); default: const exhaustiveCheck: never shape; throw new Error(Unhandled shape: ${exhaustiveCheck}); } };当团队在代码评审中新增Rectangle成员却遗漏area分支时编译器会在const exhaustiveCheck: never shape;一行直接报错——这就是判别联合为长期演进提供的类型安全保障。六、在本项目中的位置与延伸阅读本文讲解的章节位于 The Concise TypeScript Book 的第 25 节保加利亚语版 discriminated-unions.mdsidebar order 为 25同英文版一致属于类型系统主题线的核心章节。它上承字面量类型与联合类型下接穷尽性检查与以下仓库文档构成完整知识链主题仓库文档与判别联合的关系字面量类型literal-types.md判别属性取值的理论基础联合类型union-type.md判别联合的父概念类型收窄narrowing.md收窄的各类实现手段控制流分析control-flow-analysis.md收窄的底层编译原理穷尽性检查exhaustiveness-checking.md判别联合的配套安全机制never 类型never-type.md穷尽性检查的基石类型七、总结判别联合是 TypeScript 中最具性价比的类型建模工具仅靠共享判别属性 唯一字面量取值 联合组合三个要素就能获得精确的属性访问在每个分支内只暴露该成员独有的属性杜绝运行时属性访问错误自动的类型收窄switch、if、三元表达式乃至 TS 4.4 的const间接判别均可触发编译期穷尽性保障配合never类型新增联合成员时编译器强制全量更新处理逻辑。无论是状态机、网络协议解析、表单字段建模还是 Redux Action 处理判别联合都能让每种情况写清每种数据从约定升级为编译期强制的纪律。建议在项目实践中将本文的default never模板固化为团队惯用法让类型系统替你把好最后一关。赞分享文档教程【免费下载链接】typescript-bookThe Concise TypeScript Book: A Concise Guide to Effective Development in TypeScript. Free and Open Source.项目地址https://gitcode.com/gh_mirrors/typ/typescript-book点击查看免费下载相关推荐The Concise TypeScript Book 精读Discriminated Unions 判别联合类型完全指南The Concise TypeScript Book 精读Discriminated Unions 判别联合类型完全指南 判别联合Discriminate文档教程SpacetimeDB Reducer 中如何区分并处理发送者错误与内部错误SpacetimeDB Reducer 中如何区分并处理发送者错误与内部错误 在 SpacetimeDB 中写 Reducer 时一个常见任务是客户端传了非文档教程The Concise TypeScript Book 实战精读TypeScript 类型收窄Narrowing五大手段与进阶用法The Concise TypeScript Book 实战精读TypeScript 类型收窄Narrowing五大手段与进阶用法 类型收窄Narrow文档教程上一篇终极指南如何让PaperColor Theme与NERDTree、vim-airline完美集成下一篇10分钟学会创建giscus自定义主题从零到发布的完整指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表