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

TypeScript泛型嵌套函数无法基于键类型推断映射值类型

TypeScript泛型嵌套传递时类型推断失败问题

重构应用功能时遇到TypeScript类型推断问题:泛型类型在嵌套函数间传递时,TS无法识别正确类型。具体表现为finalData函数中,判断app为App.AppOne时访问data.age会报错,但直接在getDataPerApp中调用finalData时类型推断正常。期望传入app的泛型类型时,data能被正确推断出对应类型。

enum App {
  AppOne = 'app-one',
  AppTwo = 'app-two',
}

const mockedData = {
  [App.AppOne]: {
    id: '1',
    name: 'app-one',
    age: '18'
  },
  [App.AppTwo]: {
    id: '2',
    name: 'app-two',
    gender: 'male'
  }
} as const

const condition: string = 'good'

function getDataPerApp(app: App): { entries: string[] } {
  const entries: string[] = []

   if (app === App.AppOne) {
      const res = prepareData(app, mockedData[app], entries, condition)
      return { entries: res }
   } else if (app === App.AppTwo) {
      const res = prepareData(app, mockedData[app], entries, condition)
      return { entries: res }
   } else {
     return {
      entries: ['unknown data']
     }
   }

   return { entries }
}

function prepareData<T extends App>(app: T, data: typeof mockedData[T], entries: string[], condition: string): string[] {
   if ( condition === 'good') {
     const res = finalData<T>(app, data)
     entries.push(res)
   } else if ( condition === 'bad') {
    const res = finalData<T>(app, data)
    entries.push(res, 'this is bad')
   } else {
      return entries
   } 

   return entries
}

function finalData<T extends App>(app: T, data: typeof mockedData[T]): string  {
  if (app === App.AppOne) {
    return data.age  // 报错:age属性不存在于联合类型
  } else if (app === App.AppTwo) {
    return data.gender
  } else {
    return 'no'
  }
}

解决方案

问题根源在于TypeScript无法将泛型T的具体类型和data的类型做精确关联收缩。当finalData接收泛型T时,T是App的子类型,但TS无法在条件判断app === App.AppOne时,将data的类型从typeof mockedData[T]收缩为对应AppOne的具体类型。

1. 类型断言(快速修复)

在条件分支中对data做类型断言,强制指定对应类型:

function finalData<T extends App>(app: T, data: typeof mockedData[T]): string  {
  if (app === App.AppOne) {
    return (data as typeof mockedData[App.AppOne]).age
  } else if (app === App.AppTwo) {
    return (data as typeof mockedData[App.AppTwo]).gender
  } else {
    return 'no'
  }
}

2. 函数重载(类型安全方案)

为finalData定义函数重载,让TS能根据app的具体类型匹配对应的data类型:

// 重载签名:定义不同参数组合的类型
function finalData(app: App.AppOne, data: typeof mockedData[App.AppOne]): string;
function finalData(app: App.AppTwo, data: typeof mockedData[App.AppTwo]): string;
// 实现签名:实际逻辑编写
function finalData(app: App, data: typeof mockedData[App]): string  {
  if (app === App.AppOne) {
    return data.age // 此时类型推断正常
  } else if (app === App.AppTwo) {
    return data.gender
  } else {
    return 'no'
  }
}

这种方式无需泛型,直接通过重载让TS在调用时精确推断参数类型,同时在实现内部也能正确收缩类型。

3. 泛型关联映射类型(进阶方案)

通过创建映射类型强化泛型和数据类型的绑定:

type AppData = typeof mockedData;

function finalData<T extends keyof AppData>(app: T, data: AppData[T]): string  {
  if (app === App.AppOne) {
    return data.age // TS现在能正确识别data的类型
  } else if (app === App.AppTwo) {
    return data.gender
  } else {
    return 'no'
  }
}

用keyof AppData替代T extends App,让泛型T直接绑定到mockedData的键,TS能更好地关联app和data的类型。

内容的提问来源于stack exchange,提问作者Tianyi Li

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 12:59:58