如何在TypeScript的switch语句中用小写判别键推断对象联合类型?
TypeScript联合类型判别式转小写后无法正确推断类型
我希望基于name属性的值推断Option联合类型中的具体类型,但由于判别键的大小写敏感特性,遇到了TypeScript错误。尝试了两种解决方法均未生效,TypeScript仍会抛出错误。
示例代码
const toLowerCase = <T extends string>(x: T) => x.toLowerCase() as Lowercase<T>; type Option = { name: "a"; foo: "foo" } | { name: "b"; bar: "bar" } function printOption(option: Option) { switch (option.name) { case "a": console.log(option.foo) break case "b": console.log(option.bar) break } } function printOptionToLowerCase(option: Option) { switch (toLowerCase(option.name)) { case "a": // Property 'foo' does not exist on type 'Option'. // Property 'foo' does not exist on type '{ name: "b"; bar: "bar"; }'.(2339) console.log(option.foo) break case "b": // Property 'bar' does not exist on type 'Option'. // Property 'bar' does not exist on type '{ name: "a"; foo: "foo"; }'.(2339) console.log(option.bar) break } } function printOptionAsserObjectField(option: Option) { switch (option.name.toLowerCase() as Option["name"]) { case "a": // Property 'foo' does not exist on type 'Option'. // Property 'foo' does not exist on type '{ name: "b"; bar: "bar"; }'.(2339) console.log(option.foo) break case "b": // Property 'bar' does not exist on type 'Option'. // Property 'bar' does not exist on type '{ name: "a"; foo: "foo"; }'.(2339) console.log(option.bar) break } }
问题原因
TypeScript无法自动关联转换后的小写字符串和原联合类型的判别式,因为转换操作打破了TS对判别式值的追踪,导致类型缩小逻辑失效。你之前尝试的两种方法都没能建立这种关联:
printOptionToLowerCase中,toLowerCase(option.name)返回的是Lowercase<"a" | "b">,但TS无法将这个值和原option.name的具体值绑定,无法缩小option的类型。printOptionAsserObjectField中的类型断言只是告诉TS转换后的值属于"a" | "b",但同样无法建立和原option的关联,类型缩小失败。
有效解决方法
方法1:自定义类型守卫函数
手动编写类型守卫,明确转换后的值对应的原联合类型成员:
const toLowerCase = <T extends string>(x: T) => x.toLowerCase() as Lowercase<T>; type Option = { name: "a"; foo: "foo" } | { name: "b"; bar: "bar" } // 自定义类型守卫:判断option是否对应指定name的类型 function isOptionWithName<T extends Option["name"]>(option: Option, name: T): option is Extract<Option, { name: T }> { return toLowerCase(option.name) === toLowerCase(name); } function printOptionFixed(option: Option) { if (isOptionWithName(option, "a")) { console.log(option.foo); // 正确推断类型,无错误 } else if (isOptionWithName(option, "b")) { console.log(option.bar); // 正确推断类型,无错误 } }
方法2:使用映射类型预定义对应关系
创建映射类型,将小写name值映射到对应的联合类型成员,再通过类型断言辅助推断:
const toLowerCase = <T extends string>(x: T) => x.toLowerCase() as Lowercase<T>; type Option = { name: "a"; foo: "foo" } | { name: "b"; bar: "bar" } // 建立小写name到对应Option类型的映射 type LowercaseOptionMap = { [K in Lowercase<Option["name"]>]: Extract<Option, { name: Uppercase<K> }>; }; function printOptionFixed2(option: Option) { const lowerName = toLowerCase(option.name); // 通过映射类型断言option的具体类型 const typedOption = option as LowercaseOptionMap[typeof lowerName]; if (lowerName === "a") { console.log(typedOption.foo); // 类型推断正确 } else if (lowerName === "b") { console.log(typedOption.bar); // 类型推断正确 } }
内容的提问来源于stack exchange,提问作者ksantos
相关产品推荐
相关产品推荐

