Typescript:基于枚举参数值的条件函数返回类型

g2ieeal7  于 2023-02-10  发布在  TypeScript
关注(0)|答案(1)|浏览(140)

在输入一个接受单个枚举作为参数并返回一个Map器函数的工厂函数时寻找一些帮助。

// enumeration of possible partners 
enum Partner {
    Google = 'google',
    Microsoft = 'microsoft'
}

// lets say it's a domain entity, we'll map partner DTOs to it
type Entity = {
    id: string
}

// DTO and mapper function for Partner.Google
type GoogleDto = {
    google_id: string
}

function mapFromGoogle(dto: GoogleDto): Entity {
    return {
        id: dto.google_id
    }
}

// DTO and mapper function for Partner.Microsoft
type MicrosoftDto = {
    ms_id: number
}

function mapFromMicrosoft(dto: MicrosoftDto): Entity {
    return {
        id: String(dto.ms_id)
    }
}

// And here I am trying to use a conditional type
// to check which enum value is passed as an argument
// and to provide a correct return type

function toEntity<T extends Partner>(partner: T): T extends Partner.Google ? typeof mapFromGoogle : typeof mapFromMicrosoft {
    switch(partner) {
        case Partner.Google:
            return mapFromGoogle // Type '(dto: GoogleDto) => Entity' is not assignable to type 'T extends Partner.Google ? (dto: GoogleDto) => Entity : (dto: MicrosoftDto) => Entity'
        case Partner.Microsoft:
            return mapFromMicrosoft // Type '(dto: MicrosoftDto) => Entity' is not assignable to type 'T extends Partner.Google ? (dto: GoogleDto) => Entity : (dto: MicrosoftDto) => Entity'
        default:
        throw new Error('Unsupported partner')
    }
}

const e1 = toEntity(Partner.Google)({ google_id: 'id' }) 
const e2 = toEntity(Partner.Google)({ google_id: 'id', ms_id: 4 }) // 'ms_id' does not exist in type 'GoogleDto'
const e3 = toEntity(Partner.Microsoft)({ ms_id: 10 })
const e4 = toEntity(Partner.Microsoft)({ ms_id: 10, google_id: 'asd' }) // 'google_id' does not exist in type 'MicrosoftDto'
const e5 = toEntity(Partner.Google)({}) // Property 'google_id' is missing in type '{}' but required in type 'GoogleDto'
const e6 = toEntity(Partner.Microsoft)({}) // Property 'ms_id' is missing in type '{}' but required in type 'MicrosoftDto'

生成的函数按预期工作,它正确地依赖于提供的伙伴,如果提供的DTO ID无效,则会出错。
但是,当我尝试从case块中的toEntity函数返回特定的Map器函数时,TS显示错误。
问有没有人能给我指出正确的方向来解决这个案子。
我尝试过将枚举转换为联合,但也不起作用。还尝试过删除依赖于TS来推断返回的Map器函数类型的返回类型,但在这种情况下,当调用toEntity时,类型检查不起作用。

rkue9o1l

rkue9o1l1#

TypeScript类型检查器无法真正推断哪些值可以或不可以赋给依赖于尚未指定的generic类型参数的conditional type。它 * 延迟 * 此类类型的计算。在toEntity()的主体内,类型

T extends Partner.Google ? typeof mapFromGoogle : typeof mapFromMicrosoft

是一种通用的条件类型,因此编译器不确定它将是什么......它不确切地知道T是什么,因此它对T extends Partner.Google ? typeof mapFromGoogle : typeof mapFromMicrosoft几乎一无所知。
您可能认为switch/case语句会检查partner参数的不同可能性,从而帮助缩小/重新约束T,但这并没有发生,至少在TypeScript 4.9中是这样。
microsoft/TypeScript#33912是一个更好的规范特性请求,它已经开放了很长一段时间,没有任何迹象表明它何时会实现。
现在,如果你想继续,你需要绕过它。
在我看来,最好的解决方法是重构你的操作,这样它们就被表示为一个泛型 * 属性查找 *,而不是泛型switch/case。编译器理解泛型indexed access types的方式是它不理解泛型条件类型。
这里有一种写法:

const partnerMap = {
  [Partner.Google]: mapFromGoogle,
  [Partner.Microsoft]: mapFromMicrosoft
}
type PartnerMap = typeof partnerMap;
function toEntity<K extends Partner>(partner: K): PartnerMap[K] {
  return partnerMap[partner];
}

partnerMap对象将所需的toEntity()输入输出关系编码为键-值对。toEntity调用签名表明,通过查找partnerMap类型,返回类型与输入类型相关。并且实现已更改为匹配。这是因为类型检查器同意在PartnerMap类型的值中查找K类型的键将生成PartnerMap[K]类型的值。
让我们从调用方的Angular 来确保它能按预期工作:

const g = toEntity(Partner.Google); 
// const g: (dto: GoogleDto) => Entity
const m = toEntity(Partner.Microsoft);
// const m: (dto: MicrosoftDto) => Entity

看起来不错;这个部分与您的版本没有任何变化,所以所有测试用例的行为都是相同的。
Playground代码链接

相关问题