Typescript 强制函数返回类型作为基于参数的接口的关键

xkc*_*kcm 4 generics key function typescript

基本上我想要实现的是强制函数根据传递给函数的键返回正确的类型。所以如果键是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')
    }
}
Run Code Online (Sandbox Code Playgroud)

我得到的错误:

错误

TypeScript 游乐场链接

jca*_*alz 5

该类型Payloads[S]索引访问类型,依赖于尚未指定的泛型类型参数S

在您的实现中,编译器无法使用控制流分析来缩小/语句S中的类型参数。例如,它发现可以缩小为,但它不会缩小类型参数。因此它不知道在这种情况下将被分配给。编译器在这里本质上过于谨慎;它唯一乐意返回的是一个可以分配给任何值的值,它是所有值类型的交集,相当于。由于您永远不会返回这样的值,因此编译器会抱怨。switchcasekey"service1"S{ a: true }Payloads[S]Payloads[S]S{a: boolean; b: string; c: number}

GitHub 上有几个未解决的问题,需要在这里进行一些改进。例如,请参阅microsoft/TypeScript#33014 。但目前(从 TS4.6 开始),如果您必须编写以这种方式工作的代码,那么编译器将无法帮助您验证类型安全性。您需要使用类型断言之类的东西来接管责任

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')
    }
}
Run Code Online (Sandbox Code Playgroud)

或单个调用签名过载

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')
    }
}
Run Code Online (Sandbox Code Playgroud)

松开足够的东西以防止错误。这必然会让您因意外切换返回值而犯错误。但目前这是switch/可以做的最好的事情case


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

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]);
Run Code Online (Sandbox Code Playgroud)

这是有效的,因为除其他外,TypeScript 中引入了索引访问类型(在该链接中称为“查找类型”),以便在类型级别表示当您使用值级别的键索引到对象时会发生什么。也就是说,如果您有一个类似对象的t类型值T和一个类似键的k类型值,那么当使用likeK进行索引时,您读取的属性将是类型。因此,如果您希望编译器知道您有一个 type 的值,可以通过使用 type 的键索引到 type 的值来实现,如上所示。tkt[k]T[K]Payloads[S]PayloadsS

Playground 代码链接