我有一个接受键对象的函数,每个值都有一个类型,这样对于每个它的一个字段的类型决定另一个字段的类型。代码:
// We have this Alpha type and echo function...
type NoInfer<T> = [T][T extends unknown ? 0 : never]
interface Alpha<Foo extends string> {
foo: Foo
bar: `Depends on ${NoInfer<Foo>}`
}
declare const echo: <T extends string>(x: Alpha<T>) => void
echo({ foo: 'beta', bar: 'Depends on beta'})
// @ts-expect-error Trailing 2 is wrong
echo({ foo: 'beta', bar: 'Depends on beta 2'})
// Now we want a function (bravo) that takes a keyed index of Alphas...
declare const bravo: <T extends { [k: string]: Alpha<string> }>(xs: T) => void
bravo({
one: { foo: `1`, bar: `Depends on 1` },
// @ts-expect-error 1 !== 1x <-- fails
oneX: { foo: `1x`, bar: `Depends on 1` },
two: { foo: `2`, bar: `Depends on 2` },
// @ts-expect-error 2 !== 2x <-- fails
twoX: { foo: `2x`, bar: `Depends on 2` },
})
// how could this work?
正如您从“失败”评论中看到的那样,我最初可以让 Alpha 工作,但在更复杂的 Alpha 对象中我失败了。你能帮我解决这个问题吗?谢谢!
您可以这样写,这样
T
是一个对象类型,其属性是您作为类型参数传递给 string
的 Alpha
s,然后使 xs
成为 T
上的 映射类型,像这样:
declare const bravo: <T extends { [K in keyof T]: string }>(
xs: { [K in keyof T]: Alpha<T[K]> }
) => void
请注意,递归 约束
{ [K in keyof T]: string }
用于保证 T
的每个属性都是 string
而无需使用 index 签名 { [k: string]: string }
将拒绝没有索引签名的 interface 类型(参见 microsoft /TypeScript#15300 和 How to constrain a TypeScript interface to have only string property values? for more info).
总之,因为
xs
的类型是homomorphic映射类型(参见What does "homomorphic mapped type" mean?),那么当你调用函数时,编译器可以从中推断出T
(这使用被记录但新手册似乎没有提到它🤷u200d♂️)。让我们测试一下:
bravo({
one: { foo: `1`, bar: `Depends on 1` }, // okay
oneX: { foo: `1x`, bar: `Depends on 1` }, // error
// --------------> ~~~
// Type '"Depends on 1"' is not assignable to type '"Depends on 1x"'
two: { foo: `2`, bar: `Depends on 2` }, // okay
twoX: { foo: `2x`, bar: `Depends on 2` }, // error
// --------------> ~~~
// Type '"Depends on 2"' is not assignable to type '"Depends on 2x"'
})
看起来不错。如果您在支持 IntelliSense 的 IDE 中将鼠标悬停在该函数调用上,您将获得快速信息
/* const bravo: <{
one: "1";
oneX: "1x";
two: "2";
twoX: "2x";
}>(xs: {
one: Alpha<"1">;
oneX: Alpha<"1x">;
two: Alpha<"2">;
twoX: Alpha<"2x">;
}) => void */
显示
T
被推断为 {one: "1", oneX: "1x", two: "2", twoX: "2x"}
,因此针对 xs
检查 {one: Alpha<"1">, oneX: Alpha<"1x">, two: Alpha<"2">, twoX: Alpha<"2x">}
的类型,这对于 one
和 two
属性成功但对于 oneX
和 twoX
属性,给你你想要的错误。