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
相关产品推荐
相关产品推荐

