哪些工具/模式有助于创建一个可以与泛型动态复用的接口



我有一个这样的对象,我计划将其作为一个模板,接受任意数量的字段。我想设计一个接口,这样我就可以提供一个字段名称数组,并可以将其插入到这个对象中,如下所示。但我不确定我做的是正确的,还是过于复杂。

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: ''
}
}
}

游乐场链接

最新更新