ARTICLE DETAIL

资讯详情

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

TypeScript 判别联合(Discriminated Unions)实战指南:用判别属性实现精准类型收窄与穷尽性检查

TypeScript 判别联合(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》关联文档中Unions discriminées判别联合一章的深度讲解。文章以该章节的 Square/Circle/Shape 示例为主线结合仓库中 union-type.md、narrowing.md、control-flow-analysis.md、exhaustiveness-checking.md 等相邻章节系统说明判别联合的定义、判别属性的作用原理、与类型收窄的配合方式以及如何借助never类型做穷尽性检查。读完本文你将掌握用判别联合建模多种形态数据、在switch/if中安全收窄类型、并在新增分支时让编译器替你兜底的类型安全开发方案。什么是判别联合在 TypeScript 中**判别联合Discriminated Union又称 Tagged Union / 可辨识联合是一种特殊的联合类型它通过一个公共属性discriminant判别属性**来缩小联合中可能类型的集合。当联合的每个成员类型都拥有同一个属性且该属性在各自成员中被声明为不同的字面量类型时TypeScript 就能依据这个属性在运行时精确地区分每个成员。其语法基础来自两类 TypeScript 核心机制联合类型Union Type表示一个值可以是若干类型之一用|符号连接每个可能的类型参见 union-type.mdlet x: string | number; x hello; // Valid x 123; // Valid字面量类型Literal Type某个集合中的单元素类型精确对应一个 JavaScript 原始值。字符串、数字、布尔值都可以作为字面量类型参见 literal-types.mdconst a a; // String literal type const b 1; // Numeric literal type const c true; // Boolean literal type type O a | b | c; // 只允许这三个字符串值判别联合正是这两者的结合联合中每个成员都以不同的字面量值填充同一个判别属性使该属性成为运行时判断的依据。核心示例用kind判别属性建模图形原文档给出的经典示例是用一个kind属性区分正方形与圆形。kind在Square中被声明为字面量类型square在Circle中被声明为circle因此kind就是联合的判别属性discriminanttype Square { kind: square; // Discriminant size: number; }; type Circle { kind: circle; // Discriminant 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逐步拆解这个示例建模阶段Square拥有kind: square与size: numberCircle拥有kind: circle与radius: number。两个成员没有公共的形状字段唯一共有的就是判别属性kind。组合阶段type Shape Square | Circle把两者合成为联合类型一个Shape值要么是正方形、要么是圆形不可能同时是两者。消费阶段area函数以shape: Shape为参数在switch (shape.kind)中对判别属性做等值比较。TS 编译器在case square分支内将shape收窄为Square因此可以安全访问shape.size在case circle分支内收窄为Circle可以安全访问shape.radius。结果验证area(square)输出25即 5²area(circle)输出12.566370614359172即 π·2²与运行时实际计算一致。这里的核心要点是判别属性必须使用字面量类型而非string因为字面量值才是编译期可识别的标签。若把kind声明为普通string编译器将无法在分支中区分两个成员。判别联合的底层机制类型收窄Narrowing判别联合之所以能安全访问各分支的专属属性依赖的是 TypeScript 的类型收窄narrowing机制——在条件块内部将变量的类型精化为其联合类型的子集。仓库 narrowing.md 列举了多种收窄途径其中与判别联合直接相关的是等值收窄Equality narrowingEquality narrowing in TypeScript works by checking whether a variable is equal to a specific value or not, to narrow its type accordingly. It is used in conjunction withswitchstatements and equality operators such as,!,, and!to narrow down types.const checkStatus (status: success | error) { switch (status) { case success: return true; case error: return null; } };area示例中的switch (shape.kind)正是等值收窄在判别联合上的应用判别属性的字面量值square/circle与case分支的字面量标签逐一匹配编译器据此把联合收缩到单一成员。除switch外if (shape.kind square)同样可以触发收窄。为了对比narrowing.md 还介绍了另外四种收窄方式它们适用于不同场景收窄方式判别依据适用场景typeof类型守卫变量的内建 JS 类型string \| number这类基础类型联合Truthiness 收窄变量是否为真值string \| null等可空值等值收窄Equality变量是否等于某特定值switch/!//!判别联合的核心手段in运算符收窄属性是否存在于对象类型中成员间没有公共判别属性的对象联合instanceof收窄对象的构造函数基于类的联合in运算符收窄尤其值得注意当联合成员没有公共判别属性时例如Dog有breed、Cat有likesCream可以用breed in pet来判断成员类型见 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; } };相比之下判别联合让成员共享同一个判别属性代码意图更明确也更便于编译器做穷尽性检查。判别属性与控制流分析TypeScript 4.4判别联合的收窄还依赖 TypeScript 的控制流分析Control Flow Analysis。仓库 control-flow-analysis.md 指出从 TypeScript 4.4 起控制流分析不仅作用于if语句内部的代码还能作用于条件表达式以及通过const变量间接引用的判别属性访问const f2 ( obj: { kind: foo; foo: string } | { kind: bar; bar: number } ) { const isFoo obj.kind foo; if (isFoo) { obj.foo; } else { obj.bar; } };这里把判别属性比较的结果存入const isFoo在if (isFoo)分支内编译器仍能推导出obj已被收窄为{ kind: foo; foo: string }因此obj.foo可以安全访问。该文档同时强调了一个容易踩坑的限制收窄只在const变量上生效且被判别对象不能在函数体内被重新赋值。例如let isString typeof x string后再收窄会报错obj obj这类函数体内的赋值也会中断收窄详见 control-flow-analysis.md。此外条件表达式中最多分析五级间接引用。穷尽性检查用never让编译器兜底判别联合的另一个杀手级特性是穷尽性检查Exhaustiveness Checking。仓库 exhaustiveness-checking.md 定义穷尽性检查确保switch或if语句处理了判别联合的所有可能分支。其实现方式是借助never类型type 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); // This line will never be executed } };原理如下当switch覆盖了Direction的所有成员up、down后default分支中的direction已被收窄为没有任何值的类型即never。将direction赋给类型为never的变量exhaustiveCheck类型上是合法的never可赋值给任何类型反过来任何非never值都无法赋值给never。如果日后给Direction新增一个成员如left而忘记在switch中处理default分支里direction的类型将变成leftconst exhaustiveCheck: never direction就会产生编译错误提醒你补齐分支。把这一模式套用到本文的area示例上可以增强其健壮性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); default: const exhaustiveCheck: never shape; return exhaustiveCheck; } };将来若新增type Triangle { kind: triangle; base: number; height: number }并把它并入Shape编译器会立刻在default分支报错提示area尚未处理三角形——这就是判别联合带来的改一处、编译器帮你查全的维护体验。综合实战用判别联合建模网络请求状态将上述概念整合起来一个典型的判别联合实战场景是建模异步请求的多种状态在switch中逐类处理并用never兜底保证未来扩展安全type RequestState | { status: idle } | { status: loading } | { status: success; data: string[] } | { status: error; message: string }; const render (state: RequestState) { switch (state.status) { case idle: return Waiting for user action; case loading: return Loading...; case success: return Loaded ${state.data.length} items; // 此处 state 已收窄为 success 成员 case error: return Error: ${state.message}; // 此处 state 已收窄为 error 成员 default: const exhaustiveCheck: never state; return exhaustiveCheck; } };这里status就是判别属性四个不同的字符串字面量每个分支都能安全访问各自专属字段data、message且四个case全被覆盖后default中的state收敛为never。这种写法避免了大段if嵌套和运行时类型断言as让数据流在类型层面变得自解释。使用判别联合的最佳实践结合原文档示例与仓库相邻章节总结以下实践建议判别属性必须是字面量类型只有square、circle、success这类字面量字符串、数字或布尔值才能让编译器在编译期区分成员使用普通string会让收窄失效。判别属性最好语义化命名kind、type、status、state都是常见命名语义清晰即可判别属性应放在对象成员中且各成员保持一致的键名与位置。配合switch或if做等值收窄switch (shape.kind)、if (shape.kind square)都是合法触发点比较结果存入const变量后可间接引用需 TypeScript 4.4且对象不可在函数体内重新赋值见 control-flow-analysis.md。用never做穷尽性兜底在switch的default分支声明const exhaustiveCheck: never value新增联合成员而忘记处理时编译器会报错见 exhaustiveness-checking.md。判别联合优于裸联合 运行时判断相比依赖in运算符或instanceof在运行时摸索成员身份narrowing.md判别联合把身份信息显式固化在数据模型里可读性、可维护性与穷尽性保障都更强。以上代码片段均可直接复制到 TypeScript 4.4 项目中编译运行。本书在不同语言版本中均有对应章节中文版可参见 zh-cn/book/discriminated-unions.md章节编排见 table-of-contents.md。把判别联合作为建模多形态数据的默认选择配合收窄与穷尽性检查就能在开发阶段消灭一大类类型错误这也是本指南将其作为全书重点章节的原因。赞分享文档教程【免费下载链接】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的类型收窄实战 本篇为开源仓库 The Con文档教程TypeScript 可辨识联合Discriminated Unions权威指南判别属性、穷尽性检查与 Redux 实战——基于 typescript-book 深入解析TypeScript 可辨识联合Discriminated Unions权威指南判别属性、穷尽性检查与 Redux 实战——基于 typescript b教程The Concise TypeScript Book 精讲可区分联合Discriminated Unions的判别式与类型收窄实战The Concise TypeScript Book 精讲可区分联合Discriminated Unions的判别式与类型收窄实战 可区分联合Disc文档教程上一篇QMQ生产环境案例去哪儿网订单与搜索场景的深度应用下一篇REFramework-nightly 项目使用教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表