javascript - TS:基于链式方法推断文字类型
问题描述
我只剩下一件事来解锁我的流程并发布我的表单验证库的第一个试用版。
我有以下代码(当然我省略了很多东西,所以它不会变得太大)
interface Validation {
name: string
message: string
params?: Record<string, any>
test: (value: any, params?: any) => boolean
}
class MixedSchema<T> {
type!: T
validations: Validation[] = []
required(message?: string) {
this.validations.push({
name: 'required',
message: message,
test: (value: any) => {
return value === '' ? false : true
},
})
return this
}
oneOf(arrayOfValues: any[], message?: string) {
type Params = { arrayOfValues: any[] }
this.validations.push({
name: 'oneOf',
params: { arrayOfValues },
message: message,
test: (value: any, params: Params) => {
return params.arrayOfValues.includes(value)
},
})
return this
}
}
class StringSchema extends MixedSchema<string> {
email() {
// this.validations.push({ ... })
return this
}
maxWords(maxWords: number, message?: string) {
// this.validations.push({ ... })
return this
}
}
class NumberSchema extends MixedSchema<number> {
min(min: number, message?: string) {
// this.validations.push({ ... })
return this
}
round(type: 'toUp' | 'toDown' | 'closer') {
// this.validations.push({ ... })
return this
}
}
const schema = {
string() {
return new StringSchema()
},
number() {
return new NumberSchema()
},
// array, file, etc..
}
const form = {
email: schema.string().required(),
age: schema.number().min(18),
gender: schema.string().oneOf(['male', 'female']).required(),
color: schema.string().oneOf(['red', 'blue', 'green']),
}
type InferType<T> = {
[P in keyof T]: T[P] extends MixedSchema<infer TS> ? TS : never
}
type Form = InferType<typeof form>
所以我得到以下结果
但我需要尽可能地进行最真实的输入,我的意思是,类似于为 定义的架构form
,例如
interface HowNeedsToBe {
email: string
age: number | undefined
gender: 'male' | 'female'
color: 'red' | 'blue' | 'green' | undefined
}
我相信逻辑是这样的,在没有 的情况下required
,放置一个undefined
,如果有,用 的代替oneOf
论点,但我不知道如何将其发送回至,实际上我认为这个逻辑是一个烂摊子。 T
MixedSchema<T>
MixedSchema<T>
我已经研究过 typescript 中的 map 和泛型,但我承认,在将其付诸实践时,并没有什么好的结果。
如果您想尝试,这里是TS 游乐场。
解决方案
从概念上讲,您希望required()
和oneOf()
方法缩小T
;这很容易提供类型(尽管您需要类型断言来避免编译器错误,因为编译器无法验证您是否确实完成了必要的缩小)。因此required()
,在 a上调用MixedSchema<T>
应该返回 a MixedSchema<Exclude<T, undefined>>
(使用实用Exclude
程序类型undefined
从 的任何联合成员中删除T
)。并且在 的元素的元素类型中oneOf()
应该是泛型U
的arrayOfValues
,并且应该返回一个MixedSchema<U | Extract<undefined, T>>
(如果可以的话,使用Extract
实用程序类型来保持)。undefined
T
undefined
下面是它的外观(为简洁起见省略了实现):
declare class MixedSchema<T> {
type: T
validations: Validation[];
required(message?: string): MixedSchema<Exclude<T, undefined>>;
oneOf<U extends T>(arrayOfValues: U[], message?: string):
MixedSchema<U | Extract<undefined, T>>
}
不幸的是,您进行子类化的事实MixedSchema
使事情变得复杂;你想说,例如, aStringSchema
应该StringSchema
在调用之后保持某种形式的 a required()
;它不应该扩大回MixedSchema
:
declare class StringSchema<T extends string | undefined> extends MixedSchema<T> {
email(): this;
maxWords(maxWords: number, message?: string): this;
}
declare class NumberSchema<T extends number | undefined> extends MixedSchema<T> {
min(min: number, message?: string): this;
round(type: 'toUp' | 'toDown' | 'closer'): this;
}
const s = new StringSchema() // StringSchema<string | undefined>
s.email(); // okay
const t = s.required(); // MixedSchema<string>
t.email(); // error! Property 'email' does not exist on type 'MixedSchema<string>';
所以我们需要一些更复杂的东西。
这里的“正确”答案是使用microsoft/TypeScript#1213中请求的(但未实现)排序的所谓高级类型。您想说:的方法应该返回,您在某种程度上将其视为采用泛型参数的类型。所以如果是 a ,那么它应该是。但是没有直接的支持。MixedSchema<T>
required()
this<Exclude<T, undefined>>
this
this
StringSchema<T>
StringSchema<Exclude<T, undefined>>
相反,我们需要对其进行模拟,并且所有此类模拟都将涉及一些“注册”我们希望能够像泛型一样对待的类型:
type Specify<C extends MixedSchema<any>, T> =
C extends NumberSchema<any> ? NumberSchema<Extract<T, number | undefined>> :
C extends StringSchema<any> ? StringSchema<Extract<T, string | undefined>> :
MixedSchema<T>;
我们列出了MixedSchema
我们关心的所有子类,并描述了如何指定它们的类型参数。所以虽然我们不能写this<Exclude<T, undefined>>
,但我们可以写Specify<this, Exclude<T, undefined>>
,并且有同样的效果。
这是新的实现MixedSchema
:
declare class MixedSchema<T> {
type: T
validations: Validation[];
required(message?: string): Specify<this, Exclude<T, undefined>>;
oneOf<U extends T>(arrayOfValues: U[], message?: string):
Specify<this, U | Extract<undefined, T>>
}
我们可以验证它现在在子类中的行为是否正确:
const s = new StringSchema() // StringSchema<string | undefined>
s.email(); // okay
const t = s.required(); // StringSchema<string>
t.email(); // okay
让我们确保根据您的需要推断类型:
const form = {
email: schema.string().required(),
age: schema.number().min(18),
gender: schema.string().oneOf(['male', 'female']).required(),
color: schema.string().oneOf(['red', 'blue', 'green']),
}
/* const form: {
email: StringSchema<string>;
age: NumberSchema<number | undefined>;
gender: StringSchema<"male" | "female">;
color: StringSchema<"red" | "blue" | "green" | undefined>;
} */
这是一个好兆头;每个字段的泛型类型参数都以正确的方式指定。因此,您InferType
应该能够获取这些字段类型:
type Form = InferType<typeof form>
/* type Form = {
email: string;
age: number | undefined;
gender: "male" | "female";
color: "red" | "blue" | "green" | undefined;
} */
看起来不错!
推荐阅读
- android - 如何完成应用程序并返回上一屏幕
- javascript - React:如何在不改变状态的情况下根据属性过滤状态?
- arrays - 219. Contains Duplicate II - 为什么使用二分查找的解决方案适用于未排序的数组?
- php - 如何在php mysql的单个页面中多次调用存储过程
- c++ - 编写单元测试和使用 for 循环时的最佳方法?
- python - 如何检查python函数是否使用while循环?
- reactjs - 如何将按钮添加到 react-date-range
- python - 基于元数据列出 azure blob - python sdk
- python-3.x - 具有不同进程的多个 tqdm 进度条
- excel - 损坏的工作表对象