我有一个函数,它接受一个键的对象,并且每个值都有一个类型,使得对于每个,其中一个字段的类型决定了另一个字段的类型。代码:
// 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
是一个对象类型,其属性是您作为类型参数传递给Alpha
的string
,然后使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
,而不使用索引签名{ [k: string]: string }
,这将拒绝没有索引签名的接口类型(参见microsoft/TypeScript#15300和如何约束TypeScript接口只有字符串属性值?
xs
的类型是一个同态映射类型(参见什么是"同态映射类型")。mean?),那么编译器可以在调用函数时从中推断出T
(这曾经有文档记录,但新的手册似乎没有提到🤷♂️)。让我们测试一下:
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"'
})
看起来不错。如果你将鼠标悬停在启用智能感知的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
属性失败,给你你想要的错误。
Playground链接到代码