)
文档教程【免费下载链接】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点击查看免费下载never是 TypeScript 类型系统中最容易被误解的类型之一它既不等于undefined也不等于void而是代表永远不可能产生的值。本文以开源项目 The Concise TypeScript Book 中 法语版 the-never-type.md及其对应的 英文版为核心骨架结合仓库中 narrowing.md、exhaustiveness-checking.md、control-flow-analysis.md 等章节系统讲解never的语义、推断时机以及它在穷尽性检查、防御式编程中的实战价值。读完本文你将能准确识别什么时候编译器会把类型收窄为never并写出能在新增联合成员时立即报错的健壮代码。一、never是什么代表不可能产生的值的类型按照本书的定义当一个变量被收窄narrowed到某个不可能包含任何值的类型时TypeScript 编译器会推断该变量必然是never类型。这是因为never类型代表一个永远不可能被产生的值。这段话包含两个关键信息never是一种空类型——它的值域中没有任何合法值存在never通常是收窄narrowing的产物——当所有可能的类型分支都被排除后剩余空间就被推断为never。书中给出的经典示例原文保留const printValue (val: string | number) { if (typeof val string) { console.log(val.toUpperCase()); } else if (typeof val number) { console.log(val.toFixed(2)); } else { // val has type never here because it can never be anything other than a string or a number const neverVal: never val; console.log(Unexpected value: ${neverVal}); } };val的声明类型是联合类型string | number。在if/else if两个分支中typeof类型守卫type guard已经排除了string和number两种可能因此else分支中val的剩余类型空间为空——编译器将其推断为never。此时把val赋值给一个显式标注为never的变量是合法的这本身就是编译器对此处代码不可达的一种类型级证明。二、理解前置联合类型与收窄机制要理解never为什么会出现必须先理解它产生的土壤——联合类型与类型收窄。本书 union-type.md 定义联合类型Union Type是表示值可以是若干类型之一的类型使用|符号连接各候选类型。let x: string | number; x hello; // Valid x 123; // Valid而 narrowing.md 进一步说明TypeScript 收窄是在条件代码块内精化变量类型的过程。在处理联合类型变量可能具有多个类型时非常有用。本书列举了 TypeScript 识别收窄的多种途径typeof类型守卫基于变量的内建 JavaScript 类型进行判断例如typeof x number后x被收窄为number真值收窄truthiness narrowing通过判断变量是否为真值/假值收窄类型例如if (name)排除null与undefined相等性收窄equality narrowing配合switch语句与、!、、!运算符收窄类型in运算符收窄基于对象类型中是否存在某个属性来收窄例如if (breed in pet)区分Dog | Catinstanceof收窄基于构造函数判断对象是否为某类实例例如shape instanceof Square后shape被收窄为Square。收窄机制的每一次排除都在消耗联合类型中的候选成员。当所有候选成员都被消耗殆尽时剩下的类型空间就是never——这正是第一节示例中else分支里val: never的由来。因此可以这样理解never是收窄链的终点是类型空间的空集。三、never的底层语义类型系统的最底层从类型系统的语义可以推断never是 TypeScript 的最底层类型bottom type它处于所有类型的最底部具有两条核心性质never可以赋值给任何类型既然never的值域为空那么一个空集合的成员天然满足任何类型约束赋值永远不会失败任何类型都不能赋值给nevernever本身除外因为never的值域为空任何可能存在的值都无法放入这个空集合。正因为如此never常被用作不可达代码的类型标注。当编译器在一个分支中推断出某个表达式为never却仍然要求它满足某个具体类型时就会在编译期暴露逻辑漏洞——这为穷尽性检查见第五节提供了基础。需要注意的是never与unknown正好处于类型谱系的两端unknown-type.md 指出unknown代表未知类型它要求在使用前必须先做类型检查或断言且unknown只能赋值给any和unknown自身是any的类型安全替代品。而never则位于最底部两者一个代表不确定是什么一个代表什么都不是。四、never与void看似相近实则完全不同初学者最容易混淆的就是never与void。本书 void-type.md 的定义是void类型用于表示一个函数不返回值。const sayHello (): void { console.log(Hello!); };二者的本质区别在于void是返回类型层面的约定函数正常结束只是没有返回任何值返回值实际是undefinednever是值空间层面的承诺函数的执行路径根本不可能正常结束例如抛出了异常、进入了死循环或者结合收窄场景该分支在类型上就不可能到达。用一句话概括void意味着函数结束但不给值never意味着永远没有结束/永远没有值。这也是为什么never能够承担不可达代码标记的职责而void不能。五、never的核心实战穷尽性检查Exhaustiveness Checkingnever最有价值的应用场景是配合可辨识联合Discriminated Unions与switch/if语句做穷尽性检查。本书 exhaustiveness-checking.md 开宗明义穷尽性检查是 TypeScript 的一项特性用于确保可辨识联合的所有可能情况都在switch语句或if语句中被处理。其完整示例为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 } };书中对该示例的结论是never类型用于确保 default 分支是穷尽的并且当Direction类型新增值而switch语句未处理它时TypeScript 将抛出错误。5.1 为什么能编译期报错当Direction只有up | down时switch的两个case已覆盖全部成员default分支中direction被收窄为never将其赋值给const exhaustiveCheck: never是合法的程序正常编译。一旦有人把Direction扩展为up | down | leftdefault分支中的direction类型就不再是never而是left。此时const exhaustiveCheck: never direction;就会触发编译错误——left不能赋值给never。开发者不得不回到switch中补上case left的处理逻辑。错误在编译期暴露而不是等到运行时悄悄落入 default 分支。5.2 与可辨识联合结合本书 discriminated-unions.md 定义了可辨识联合可辨识联合Discriminated Unions是联合类型的一种它使用一个公共属性称为判别属性 discriminant来缩小联合中可能类型的集合。type 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将二者组合就构成了 TypeScript 社区中最经典的可扩展类型 穷尽处理模式新增一种形状如Triangle时area的switch若未覆盖编译器会在never断言处报错强制开发者同步更新所有处理逻辑避免新增类型却忘记处理的运行时事故。5.3 穷尽性检查的常见变体在if/else链末尾使用never断言与第一节printValue示例完全一致else分支中的const neverVal: never val;即穷尽性断言封装为辅助函数可以定义一个assertNever(value: never): never形式的工具函数在非穷尽时抛错并利用never类型使后续代码不可达让错误信息更友好作为函数返回值类型用: never标注永不返回的辅助函数如抛错函数让编译器理解调用点之后的代码不可达。六、控制流分析与never的推断时机never的推断依赖 TypeScript 的控制流分析Control Flow Analysis。本书 control-flow-analysis.md 定义TypeScript 的控制流分析是一种静态分析代码流以推断变量类型的方式编译器可根据分析结果在需要时收窄变量类型。该章节特别指出一个版本演进事实在 TypeScript 4.4 之前代码流分析只应用于if语句内的代码从 TypeScript 4.4 开始它也可以应用于条件表达式以及通过const变量间接引用的判别属性访问。const f1 (x: unknown) { const isString typeof x string; if (isString) { x.length; } }; const f2 ( obj: { kind: foo; foo: string } | { kind: bar; bar: number } ) { const isFoo obj.kind foo; if (isFoo) { obj.foo; } else { obj.bar; } };同时书中也给出了收窄不生效的反例有助于理解never推断的边界条件const f1 (x: unknown) { let isString typeof x string; if (isString) { x.length; // Error, no narrowing because isString it is not const } }; const f6 ( obj: { kind: foo; foo: string } | { kind: bar; bar: number } ) { const isFoo obj.kind foo; obj obj; if (isFoo) { obj.foo; // Error, no narrowing because obj is assigned in function body } };由此可知never的推断并非魔法只有当收窄条件是稳定且可追踪的如const绑定的判断结果、未被重新赋值的变量控制流分析才能把剩余类型空间精确收窄到never。书中还提示条件表达式中的间接引用最多分析五层。理解这些边界有助于避免写出该收窄却没收窄的代码。七、与类型谓词、unknown的配合使用never还经常出现在类型守卫相关的场景中。本书 type-predicates.md 介绍类型谓词Type Predicates类型谓词是返回布尔值的函数用于将变量的类型收窄到更具体的类型。const isString (value: unknown): value is string typeof value string; const foo (bar: unknown) { if (isString(bar)) { console.log(bar.toUpperCase()); } else { console.log(not a string); } };该章节还记录了 TypeScript 5.5 的重要改进TypeScript 5.5 会在.filter等函数中自动推断类型谓词如x is T从而知道undefined等值何时被移除——带来更精确的类型和更少的错误这适用于清晰的检查如x ! undefined但不适用于!!x这类含糊的写法。const nums [1, null, 2].filter(x x ! null);在 5.5 之前filter返回的数组类型不会剔除null5.5 之后类型系统自动推断出x is T形式的谓词nums被推断为number[]。这与never的关系在于类型谓词的else分支、以及剔除后剩余的联合成员最终都会归结到never的推断上——当类型谓词把联合中的所有候选成员都判定掉剩余空间就是never。在防御式编程中一个常见的组合是函数参数声明为unknown强制先收窄再使用见 unknown-type.md内部用typeof守卫逐层收窄最后在else分支用never断言兜底——这既保证了运行时安全也让所有未预期的输入在编译期被标记为不可达。八、never使用自查清单与常见误区结合以上章节整理一份实战自查清单else分支出现never是好事说明你的类型守卫已经覆盖了所有分支代码是穷尽的never不等于void函数不返回值用void永远不结束/不可能到达的路径才用never穷尽性检查记得用never断言在switch的default或if/else的收尾分支写上const exhaustiveCheck: never value;让新增联合成员时编译报错收窄条件的稳定性影响推断优先使用const保存判断结果避免在函数体内重新赋值目标变量否则控制流分析无法收窄到nevernever与unknown是两端unknown表示未知但有值never表示确定没有值不要把两者混用。常见误区提醒不要试图主动使用never作为常规变量的类型注解因为它不能容纳任何值never的价值不在于被显式写出而在于编译器在穷尽判断、不可达代码检测中的自动化应用——你在代码中做的最重要的一件事是为never的出现创造条件完整覆盖联合成员的分支 稳定的收窄条件 穷尽性断言其余交给类型系统。九、小结never是 TypeScript 收窄机制的自然终点联合类型的候选成员被逐一排除后剩余空间必然是never而 exhaustiveness-checking.md 所示的default 分支 never断言模式则把它从类型系统的理论概念变成了编译期防御机制——联合类型每新增一个成员未处理的分支就会立刻报错。掌握never意味着你不仅理解了 TypeScript 的类型收窄还掌握了让类型系统替你守住所有情况都被处理这条底线的工程化方法。建议继续阅读仓库中的 narrowing.md、discriminated-unions.md 与 control-flow-analysis.md 章节把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 精读never 类型从类型收窄到穷尽性检查的完整指南The Concise TypeScript Book 精读never 类型从类型收窄到穷尽性检查的完整指南 本篇围绕开源项目 The Concise Typ文档教程The Concise TypeScript Book 中的 never 类型从空集合收窄到穷尽性检查The Concise TypeScript Book 中的 never 类型从空集合收窄到穷尽性检查 本篇围绕 The Concise TypeScript文档教程The Concise TypeScript Book 精读深入理解 never 类型与穷尽性检查The Concise TypeScript Book 精读深入理解 never 类型与穷尽性检查 never 类型是 TypeScript 类型系统中一个看文档教程上一篇Kotaemon 文档聊天故障排除完整指南从启动失败到聊天无响应的分层排查法下一篇Notepad4高级功能详解正则表达式搜索、Base64编解码与API列表的终极指南 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考