我有一个这样的对象,我计划将其作为一个模板,接受任意数量的字段。我想设计一个接口,这样我就可以提供一个字段名称数组,并可以将其插入到这个对象中,如下所示。但我不确定我做的是正确的,还是过于复杂。
let formResponse: FormResponsePayload<["email", "username"]> = {
status: 'ERROR',
fieldsWithError: ['email', 'username'], //empty or filled with the fields corresponding to keys of fields
fields: {
email: {
field_name: 'email',
status: 'error',
message: ''
},
username: {
field_name: 'email',
status: 'error',
message: ''
}
}
}
我想出的答案是
//i was looking for something that could unpack my generic of string[]. i found Unpacked on another Stack answer. not totally sure how it works.
type Unpacked<T> = T extends (infer U)[] ? U : T;
interface CheckField<A extends string> {
field_name: A,
status: string,
message: string
}
export interface FormResponsePayload<A extends string[]> {
status: 'ERROR' | 'SUCCESS'
fieldsWithError: Unpacked<A>[],
fields: {
[key in Unpacked<A>]: CheckField<Unpacked<A>>
}
}
你确实走在了正确的轨道上!
请注意,当您指定泛型类型时,它仍然应该是一个类型,因此在FormResponsePayload<["email", "username"]>
中,泛型类型部分不能是实际的数组,必须将其转换为类型。
例如:
const fieldNames = ['email', 'username'] as const; // Assert as const to infer a tuple instead of an array string []
let formResponse: FormResponsePayload<typeof fieldNames>
然后为了获得可能的元组值;Unpacked";公用事业类型。TypeScript在数组/元组上提供索引访问类型:
type FieldNames = typeof fieldNames[number]
// ^? type FieldNames = "email" | "username"
然后你的通用界面可以写为:
interface FormResponsePayload<FieldNames extends readonly string[]> {
status: 'ERROR' | 'SUCCESS'
fieldsWithError: FieldNames[number][],
fields: {
[key in FieldNames[number]]: CheckField<key>
}
}
让我们测试一下它的用法:
const a: FormResponsePayload<typeof fieldNames> = {
status: 'SUCCESS',
fieldsWithError: ['email'], // TS catches typos
fields: { // TS requires all field names
email: {
field_name: 'email', // TS enforces same value as the field name
status: '',
message: ''
},
username: {
field_name: 'username',
status: '',
message: ''
}
}
}
我们甚至可以通过对可能的字段名使用并集来进一步简化声明,就像对许多其他TypeScript实用程序类型(例如Pick<Todo, "title" | "completed">
、Omit<Todo, "completed" | "createdAt">
等(所做的那样:
// A union of string literals actually extends string
interface FormResponsePayload2<FieldNamesUnion extends string> {
status: 'ERROR' | 'SUCCESS'
fieldsWithError: FieldNamesUnion[],
fields: {
[key in FieldNamesUnion]: CheckField<key>
}
}
它的使用也略有简化(不再需要typeof
(:
const b: FormResponsePayload2<'email' | 'username'> = {
status: 'SUCCESS',
fieldsWithError: ['email'], // TS catches typos
fields: { // TS requires all field names
email: {
field_name: 'email', // TS enforces same value as the field name
status: '',
message: ''
},
username: {
field_name: 'username',
status: '',
message: ''
}
}
}
游乐场链接