类型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
,那么当使用k
类t[k]
索引t
时,您读取的属性将是类型T[K]
.因此,如果你想让编译器知道你有一个Payloads[S]
类型的值,你可以通过索引到一个Payloads
类型的值和一个S
类型的键来实现,如上所示.
Playground link to code