基本上,我试图实现的是强制函数根据传递给函数的键返回正确的类型.因此,如果键是service1,正确的返回类型应该是Payloads['service1'].我怎样才能做到这一点?

interface Payloads {
    service1: {
        a: boolean;
    };
    service2: {
        b: string;
    };
    service3: {
        c: number;
    };
};

const createPayload = <S extends keyof Payloads>(key: S): Payloads[S] => {
    switch (key) {
        case 'service1': return { a: true }
        case 'service2': return { b: 'e' }
        case 'service3': return { c: 3 }
        default: throw new Error('undefined service')
    }
}

我得到的错误是:

error

TypeScript playground link

推荐答案

类型Payloads[S]indexed access type,它依赖于尚未指定的generic类型参数S.

在您的实现中,编译器无法使用control flow analysis来缩小switch/case语句中的类型参数S.它看到key可以缩小到例如"service1",但它没有缩小类型参数S.因此,它不知道在这种情况下{ a: true }会被分配给Payloads[S].编译器在这里基本上过于谨慎;它唯一乐意返回的是一个值,无论S是什么,它都可以分配给Payloads[S],这是所有值类型中的intersection,相当于{a: boolean; b: string; c: number}.由于从未返回这样的值,编译器会抱怨.

GitHub中有几个有待改进的问题.例如,参见microsoft/TypeScript#33014.不过,就目前而言(从TS4.6开始),如果您必须以这种方式编写代码,那么编译器将无法帮助您验证类型安全性.你需要使用type assertions之类的工具来承担责任

const createPayloadAssert = <S extends keyof Payloads>(key: S): Payloads[S] => {
    switch (key) {
        case 'service1': return { a: true } as Payloads[S]
        case 'service2': return { b: 'e' } as Payloads[S]
        case 'service3': return { c: 3 } as Payloads[S]
        default: throw new Error('undefined service')
    }
}

或者一个电话签名overload

function createPayloadOverload<S extends keyof Payloads>(key: S): Payloads[S];
function createPayloadOverload(key: keyof Payloads) {
    switch (key) {
        case 'service1': return { a: true };
        case 'service2': return { b: 'e' };
        case 'service3': return { c: 3 };
        default: throw new Error('undefined service')
    }
}

足够放松以防止错误.这必然会让您在不小心切换返回值时出错.但就目前而言,这是switch/case的最佳 Select .


如果您愿意将实现重构为编译器可以实际验证代码安全性的形式,可以通过索引到对象中来实现:

const createPayload = <S extends keyof Payloads>(key: S): Payloads[S] => ({
    service1: { a: true },
    service2: { b: 'e' },
    service3: { c: 3 }
}[key]);

const createPayloadBad = <S extends keyof Payloads>(key: S): Payloads[S] => ({
    service1: { a: true },
    service2: { a: true }, // <-- error!
    service3: { c: 3 }
}[key]);

这是因为,除其他外,索引访问类型为introduced to TypeScript(在该链接中称为"查找类型"),以表示at the type level当使用键at the value level索引到对象时会发生什么.也就是说,如果您有类型为T的对象类值t和类型为K的键类值k,那么当使用kt[k]索引t时,您读取的属性将是类型T[K].因此,如果你想让编译器知道你有一个Payloads[S]类型的值,你可以通过索引到一个Payloads类型的值和一个S类型的键来实现,如上所示.

Playground link to code

Typescript相关问答推荐

如何将http上下文附加到angular中翻译模块发送的请求

在TypScript手册中可以视为接口类型是什么意思?

相同端点具有不同响应时的RTKQuery类型定义

您可以创建一个类型来表示打字员吗

动态调用类型化函数

我可以为情态车使用棱角形的路由守卫吗?

TypeScrip:逐个 case Select 退出noUncheck kedIndexedAccess

如何解决类型T不能用于索引类型{ A:typeof A; B:typeof B;. }'

被推断为原始类型的交集的扩充类型的交集

Angular 15使用react 式表单在数组内部创建动态表单数组

使用动态输出类型和泛型可重用接口提取对象属性

如何将TS泛型用于react-hook-form接口?

类型';字符串|数字';不可分配给类型';未定义';.类型';字符串';不可分配给类型';未定义';

判断映射类型内的键值的条件类型

如何在脚本中输入具有不同数量的泛型类型变量的函数?

如何避免从引用<;字符串&>到引用<;字符串|未定义&>;的不安全赋值?

作为函数参数的联合类型的键

编剧- Select 下拉框

递归地排除/省略两种类型之间的公共属性

如何在故事书的Typescript 中编写decorator ?