Typescript 中没有特定的Test
种文字可以按照你想要的方式工作.您可以编写一个generic类型Test<K>
,其中K
表示特定对象的literal types个键中的union个.
但是为了让Test
成为一个特定的类型,你基本上需要说:"someK
中的Test<K>
我不在乎".这将需要microsoft/TypeScript#14466中所要求的所谓的100.如果这些类型存在于类型脚本中,您可能会编写type Test = <exists K> GenericTest<K>
,但它们不存在于类型脚本中(在大多数其他语言中也不存在,这不是类型脚本的一个特殊缺陷),所以您不能这样做.有一些方法可以在类型脚本中编码这样的类型,但它们相当于类似Promise
的抽象,并且只对某些用例有用.
所以没有办法写
const x: Test = ⋯;
直接go 吧.相反,我们需要涉及泛型.
让我们看一下泛型类型Test<K>
.根据您的描述,我建议使用比您的版本更简单的方法,不需要使用conditional types或try 拆分数组:
type Fields<K extends string> = readonly ({
type: "string" | "number" | "email"
id: K
})[]
interface Test<K extends string> {
fields: Fields<K>
fieldValues: Record<K, any>
}
然后你的x
值将是Test<"field1" | "field2" | "my_field">
类型.现在,如果你能写一些像这样的东西就好了
// not valid TS, don't try this
const x: Test<infer> = {⋯}
让编译器为您推断泛型类型参数K
,而不是强制您将其写出来.对于这一点,有一个长期开放的功能请求,但到目前为止,它还不是语言的一部分.目前,只有在调用泛型function时才能推断泛型类型参数.
这意味着我们可以编写一个小的实用帮助函数test()
来返回它的输入,通过调用该函数,类型参数被推断出来. 所以看起来
const x = test({⋯});
然后x
仍然是Test<"field1" | "field2" | "my_field">
类型,而不需要你自己写出来.该函数非常简单:
const test = <K extends string>(
t: Test<K>) => t;
这是可行的,因为我编写Test<K>
的方式使得可以很容易地从类型Test<K>
的值中推断出K
.让我们来演示一下:
const x = test({
fields: [
{ id: "field1", type: "string" },
{ id: "field2", type: "number" },
{ id: "my_field", type: "email" }
],
fieldValues: {
field1: "John",
field2: "John",
my_field: "@",
}
});
type X = typeof x;
// ^? type X = Test<"field1" | "field2" | "my_field">
看上go 不错.请注意,我们可以使用the TypeScript typeof
operator来获得x
类型的名称,而不必写出类型参数.因此,上面的代码展示了如何获取一个对象,让编译器验证它与Test
匹配,然后获取它的类型.
它只表明有效对象有效.让我们看看无效对象会发生什么:
const badX1 = test({
fields: [
{ id: "field1", type: "string" },
{ id: "field2", type: "number" },
{ id: "my_field", type: "email" }
],
fieldValues: { // error!
//~~~~~~~ <--
// Property 'field2' is missing
field1: "John",
my_field: "@",
}
});
const badX2 = test({
fields: [
{ id: "field1", type: "string" },
{ id: "field2", type: "number" },
{ id: "my_field", type: "email" }
],
fieldValues: {
field1: "John",
field2: "John",
field3: "John", // error!
//~~~~
// Object literal may only specify known properties
my_field: "@",
}
});
因此,如果fieldValues
的密钥丢失或多余,您会收到警告.
你只能靠这么近了.如果你真的想隐藏泛型并使其特定,你可以编码一个存在泛型类型.
参见Defining an array of differing generic types in TypeScript和/或Typescript generics: How to implicitly derive the type of a property of an object from another property of that object?,了解有关存在泛型及其在TypeScript中编码的更多信息. 它可能看起来像这样:
type SomeTest = <R>(cb: <K extends string>(t: Test<K>) => R) => R;
const someTest = <K extends string>(t: Test<K>): SomeTest => cb => cb(t);
const someX = someTest({
fields: [
{ id: "field1", type: "string" },
{ id: "field2", type: "number" },
{ id: "my_field", type: "email" }
],
fieldValues: {
field1: "John",
field2: "John",
my_field: "@",
}
});
因此,现在someX
是一个接受回调的函数,该回调接受任何K
的Test<K>
,并返回该回调的结果.它保持着与x
相同的价值,但你永远无法访问它.你只能通过一些函数来touch 它,对于K
来说,它是一些不透明的东西.例如,您可以索引到fieldValues
:
const str = someX(t => t.fields.map(f => t.fieldValues[f.id])).join(":");
console.log(str) // "John:John:@"
如果你查看回调函数内部,f.id
是K
类型,t.fieldValues
是Record<K, any>
类型,不管K
是什么. 所以不管K
是什么,你都只能做有意义的操作.你永远无法访问t.fieldValues.field1
,因为它只在"field1" extends K
时有效,而你不知道K
.
如果您需要的是与JSON兼容的类型,这种方法不太可能有帮助,因为函数不是.但对于有其他用例的future 读者来说,这可能是一种可行的方式.通用效用函数方法在很多用例中都很有用.但是,如果用例要求只有一个特定的对象类型表示未知K
的Test<K>
,那么这在TypeScrip中是不可能的,除非语言中添加了其他特性.
Playground link to code