如何让switch语句收窄TypeScript泛型函数的返回类型?
TypeScript中switch语句无法正确收窄泛型函数返回类型的解决方法
有如下TypeScript代码,尝试根据传入的Foo接口的键,从localStorage获取并验证对应类型的值,但编译时出现类型不匹配错误:
interface Foo { bar: 'a' | 'b', baz: number } function getFooValue<K extends keyof Foo>(key: K): Foo[K] { const storedValue = localStorage.getItem(key); switch (key) { case "bar": if (storedValue !== 'a' && storedValue !== 'b') { throw TypeError("Unknown bar"); } return storedValue; // type is 'a' | 'b' case "baz": const parsedVal = parseInt(storedValue === null ? "NaN" : storedValue); if (isNaN(parsedVal)) { throw TypeError("Unknown baz") } return parsedVal; // type is number default: throw Error(`Unknown`); } } const v: 'a' | 'b' = getFooValue('bar'); const u: number = getFooValue('baz');
编译时的错误信息:
Type '"a" | "b"' is not assignable to type 'Foo[K]'. Type '"a"' is not assignable to type 'Foo[K]'. Type 'string' is not assignable to type 'never'. Type 'number' is not assignable to type 'Foo[K]'. Type 'number' is not assignable to type 'never'.
已知传入的key始终为字面量字符串,编译器可进行类型推导,以下是两种解决方法:
方法一:类型断言
在每个分支的return语句中,将返回值断言为Foo[K]。由于我们已经通过条件判断确保了返回值符合对应键的类型,这个断言是安全的:
interface Foo { bar: 'a' | 'b', baz: number } function getFooValue<K extends keyof Foo>(key: K): Foo[K] { const storedValue = localStorage.getItem(key); switch (key) { case "bar": if (storedValue !== 'a' && storedValue !== 'b') { throw new TypeError("Unknown bar"); } return storedValue as Foo[K]; case "baz": const parsedVal = parseInt(storedValue === null ? "NaN" : storedValue); if (isNaN(parsedVal)) { throw new TypeError("Unknown baz") } return parsedVal as Foo[K]; default: throw new Error(`Unknown key: ${key}`); } } const v: 'a' | 'b' = getFooValue('bar'); const u: number = getFooValue('baz');
方法二:函数重载
通过定义函数重载签名,明确指定每个键对应的输入输出类型,让编译器直接匹配具体分支的类型关系,无需额外断言:
interface Foo { bar: 'a' | 'b', baz: number } // 重载签名:明确每个键对应的返回类型 function getFooValue(key: 'bar'): 'a' | 'b'; function getFooValue(key: 'baz'): number; // 实现签名:处理通用逻辑 function getFooValue(key: keyof Foo): Foo[keyof Foo] { const storedValue = localStorage.getItem(key); switch (key) { case "bar": if (storedValue !== 'a' && storedValue !== 'b') { throw new TypeError("Unknown bar"); } return storedValue; case "baz": const parsedVal = parseInt(storedValue === null ? "NaN" : storedValue); if (isNaN(parsedVal)) { throw new TypeError("Unknown baz") } return parsedVal; default: throw new Error(`Unknown key: ${key}`); } } const v: 'a' | 'b' = getFooValue('bar'); const u: number = getFooValue('baz');
方法对比
- 类型断言:代码改动最小,适合简单场景,需开发者自行保证断言的正确性。
- 函数重载:类型定义更清晰,编译器能提供更精准的类型检查,适合复杂场景或需要明确对外类型签名的情况。
内容的提问来源于stack exchange,提问作者xuhdev
相关产品推荐
相关产品推荐

