我要正确键入以下对象。这里的key
是接口的键,T
是字符串,数字或布尔值,而E
是某些键和字符串值的对象。问题是E
对于对象的每个条目都是不同的,我想保留每个对象的类型信息。
{
[key]: (errorMessage?: E) => Validation<T, E>
}
例如,我可能会有类似的东西。
interface StudentSchema {
name: string;
id: string;
country: string;
age: number;
}
interface Validation<T, E> {
foo: string;
bar: number;
validations: (errorMessages? E) => SchemaValidation<T>;
}
interface SchemaValidation<T> {
setIsRequired(message?: string) => SchemaValidation<T>;
setMatches(regex: any, message?: string) => SchemaValidation<T>;
setMax(max: number, message?: string) => SchemaValidation<T>;
isValid (data: T) => boolean;
}
class StringSchemaValidation extends SchemaValidation<string> { /* ... */ }
class NumberSchemaValidation extends SchemaValidation<number> { /* ... */ }
const StudentValidations: Record<keyof StudentSchema, Validation<T, E>> = {
name: {
foo: 'AAA',
bar: 1,
validation: (errorMessages?: { required: string }): SchemaValidation<string> =>
new SchemaValidation()
.setIsRequired(errorMessages?.required),
},
id: {
foo: 'BBB',
bar: 2,
validation: (errorMessages?: { required: string; match: string; max: (max: number) => string }): SchemaValidation<string> =>
new SchemaValidation()
.setIsRequired(errorMessages?.required)
.setMatches(/[a-zA-Z0-9]+/, errorMessages?.match)
.setMax(30, errorMessage?.max),
},
country: {
foo: 'CCC',
bar: 3,
validation: (errorMessages?: { required: string }): SchemaValidation<string> =>
new SchemaValidation()
.setIsRequired(errorMessage?.required),
},
age: {
foo: 'DDD',
bar: 4,
validation: (errorMessages?: { required: string }): SchemaValidation<number> =>
new NumberSchemaValidation()
.setIsRequired(errorMessage?.required),
},
};
首先,这不会编译,因为我没有为E
指定StudentValidations
,但是我不知道要放什么,因为每个条目的内容都不相同。我可以使用any
或类似的东西,但是当我访问validations
函数时,我会丢失类型信息。
我想要的实际上是将类型声明“委托”给实现,但这似乎是不可能的。有没有办法实现我想要的?或者,也许有另一种方法可以实现这一目标?
答案 0 :(得分:0)
我认为这里有两个问题。在从Validation<T, E>
之类的接口派生的StudentSchema
中,它具有与StudentSchema
相同的键,对于每个键K
,T
类型是相应的属性StudentSchema[K]
。这部分是mapped types的完美用例。看起来像这样:
/* type ValidationFor<T> = { [K in keyof T]: Validation<T[K], ...>}; */
/* type ValidationForStudentSchema = ValidationFor<StudentSchema>; */
其中...
是我们要为E
做的未知的事情。您可以在此处执行的操作是提供另一个对象类型E
,该对象类型具有与StudentSchema
相同的键,其属性用于插入{中的E
{1}}。看起来像这样:
Validation
然后您的示例type ValidationFor<T, E extends Record<keyof T, any>> =
{ [ K in keyof T ]: Validation<T[K], E[K]> };
type ValidationForStudentSchema<E extends Record<keyof StudentSchema, any>> =
ValidationFor<StudentSchema, E> */
将具有以下类型:
StudentValidations
但是这要么需要我们明确写出const StudentValidations: ValidationFor<StudentSchema, {
name: { required: string },
country: { required: string; },
age: { required: string; },
id: { required: string; match: string; max: (max: number) => string; }
}> = ...;
对象类型,要么尝试让编译器进行推断,这两种方法都不是一件特别有趣的事情。
相反,我建议我们放宽对E
属性的约束,并故意使用any
类型的“一切”:
E
那行得通; /* type ValidationFor<T> = { [K in keyof T]: Validation<T[K], any> }; */
会与您想要的值匹配,但这太“让人忘记”了;因为它将使用ValidationFor<StudentSchema>
而不是您提供的实际any
属性。为了使编译器记忆犹新,我们制作了一个generic辅助函数,该函数仅采用E
的通用类型的值:
extends ValidationFor<StudentSchema>
如果您将与const valsForStudentSchema =
<U extends { [K in keyof StudentSchema]: Validation<StudentSchema[K], any> }>(
valMap: U
) => valMap;
不匹配的参数传递给valsForStudentSchema()
,则会收到错误消息。如果您传递匹配的参数 ,则编译器将返回该参数而不会将其扩展为ValidationFor<StudentSchema>
并且会忘记。
赞:
ValidationFor<StudentSchema>
请注意,您询问是否要与其他模式(不仅仅是const StudentValidations = valsForStudentSchema({
name: {
foo: 'AAA',
bar: 1,
validation: (errorMessages?: { required: string }): SchemaValidation<string> =>
new StringSchemaValidation()
.setIsRequired(errorMessages?.required),
},
id: {
foo: 'BBB',
bar: 2,
validation: (errorMessages?: { required: string; match: string; max: (max: number) => string }): SchemaValidation<string> =>
new StringSchemaValidation()
.setIsRequired(errorMessages?.required)
.setMatches(/[a-zA-Z0-9]+/, errorMessages?.match)
.setMax(30, errorMessages?.max(30)),
},
country: {
foo: 'CCC',
bar: 3,
validation: (errorMessages?: { required: string }): SchemaValidation<string> =>
new StringSchemaValidation()
.setIsRequired(errorMessages?.required),
},
age: {
foo: 'DDD',
bar: 4,
validation: (errorMessages?: { required: string }): SchemaValidation<number> =>
new NumberSchemaValidation()
.setIsRequired(errorMessages?.required),
},
});
)一起使用。我们可以使用currying来做到这一点,方法是创建一个采用模式类型的泛型函数,并返回另一个生成StudentSchema
该类型的泛型函数:
ValidationFor
您可以将其与其他类似类型的产品一起使用:
const validationFor = <T>() => <U extends { [K in keyof T]: Validation<T[K], any> }>(
valMap: U
) => valMap;
const StudentValidations = validationFor<StudentSchema>()({
// ... put the same object here as before
});
好的,希望能有所帮助;祝你好运!