TypeScript:为何这一简单类型推断会失效?
先看两段使用const x: never做类型穷尽检查的TypeScript代码:
function bad<T extends 'doAfter'>(instance: Record<string, any>, fnName: T) { switch (fnName) { case 'doAfter': break; default: const x: never = fnName; // 这里TypeScript会报错:T无法赋值给never } return null; } function good(instance: Record<string, any>, fnName: 'doBefore' | 'doAfter') { switch (fnName) { case 'doAfter': case "doBefore": break; default: const x: never = fnName; // 这里检查正常通过 } return fnName; }
核心疑问
明明两个函数都没有遗漏分支,且调用时都无法传入不符合类型约束的参数,为什么bad函数的穷尽检查会报错,而good函数却能正常通过?
差异原因解析
泛型约束的类型不确定性:
bad函数的泛型约束T extends 'doAfter',并不意味着T就严格等于'doAfter'——TypeScript的泛型设计允许T是'doAfter'的任意子类型(哪怕实际不存在更窄的字符串字面量子类型)。在函数内部进行类型检查时,TypeScript会基于约束的范围判断:T可能是任何满足extends 'doAfter'的类型,而非固定的'doAfter'字面量。因此在default分支中,fnName的类型是T,无法被确定为never,自然不能赋值给const x: never。联合类型的确定性枚举:
good函数的参数类型是明确的联合类型'doBefore' | 'doAfter',TypeScript可以完全枚举所有可能的成员。当switch分支覆盖了联合类型的所有成员后,TypeScript能确定default分支的fnName类型是never(没有未处理的情况),因此赋值给const x: never完全合法。
补充说明
虽然调用两个函数时,都无法直接传入不符合约束的参数(除非用as any或//@ts-ignore绕过检查),但函数内部的类型检查逻辑是不同的:泛型类型参数的具体类型是在调用时确定的,而函数内部的检查是基于泛型约束的范围;联合类型则是固定的成员集合,TypeScript可以提前完成穷尽性验证。
内容的提问来源于stack exchange,提问作者Regular Jo

