You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 20:33:20