你可以这样写T
是一个对象类型,其属性是string
您作为类型参数传递给Alpha
,然后使xs
a 映射类型 https://www.typescriptlang.org/docs/handbook/2/mapped-types.html over T
, 像这样:
declare const bravo: <T extends { [K in keyof T]: string }>(
xs: { [K in keyof T]: Alpha<T[K]> }
) => void
请注意,递归约束 https://www.typescriptlang.org/docs/handbook/2/generics.html#generic-constraints { [K in keyof T]: string }
用于保证T
is string
不使用索引签名 https://www.typescriptlang.org/docs/handbook/2/objects.html#index-signatures { [k: string]: string }
这会拒绝界面 https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#interfaces没有索引签名的类型(参见微软/TypeScript#15300 https://github.com/microsoft/TypeScript/issues/15300 and 如何限制 TypeScript 接口仅具有字符串属性值? https://stackoverflow.com/q/73954300/2887218了解更多信息)。
无论如何,因为类型xs
is a 同态映射类型(参见“同态映射类型”是什么意思? https://stackoverflow.com/q/59790508/2887218),那么编译器可以推断T
当你调用该函数时(这曾经是记录在案 https://www.typescriptlang.org/docs/handbook/advanced-types.html#inference-from-mapped-types但新的手册好像没有提到????♂️)。让我们测试一下:
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
and two
属性但失败了oneX
and twoX
属性,为您提供您想要的错误。
Playground 代码链接 https://www.typescriptlang.org/play?#code/C4TwDgpgBAcg9gSQHYDMICcA8AVAfFAXigG1sBdUqCAD2AiQBMBnKAVyQGsk4B3JKAPxQADFABcUJBABuGMgCh5ASyR10KAIYBjaAEEANmAAWGzADE4cKrXrMoTYOhUBzfAG95UKCksSLcTygAIw10CQADABEISEYWOH4AEjd4ZDQsf1wAX3D5LMUGCC19UOgtBIcqLSM4CRxrOjj7RxdcAApqCQNjUzwASkJ8aTglBkUimra3b18oAHIgiGANOYAaYNCJOejYuwTgpZWoLL75Cbgpmdr5xeW1jbD5ndt4-luNKAAmOePT+ULiqUoOUkJUgugNMM6tgGi8oNNiABpKAqKAcCAgOAoKDkCQOJxIZzHdqBahMCQI5Go9GY7G4qDdEw4JFkfD5AYEIYjMbycGQi4eLwJCAUq4RACM4XWIUeURicP2kuO6ygAHpVVA4BwNCBAsKABqinzXcLi6hSh4RZ5NRXhZVqjUYdBwdCBdVQAC0Xu9Pp9+AAfoG3RrsOBoHMAETWvb8cURn5KFjcYBQDRMJhKZxIDRBfTQYBWUCQeZR+U22PUeOBYA8a7TY0RT4WmVWssxr52rLrd1anXV2uG+FiqDhT7m6WbEfR14d+3up0u4Oe30rr0BoNed2h4uR6ea-ifeMopNwFNpjNZnN5qAFm9hkt7-ZjqsnRSqgBUwIqKb5UKgmEFLx9xFKAIzjABuQIhSkQcwMrSCgJvWsJAjQ8EKAms4Fg59IKydoyQpKDgK6QwmTAiNcHQ6CIEHRlTDgiiqKQ646MwVDGKIzDaNI+jn0ovIOS5UYoHfVUgA