gpt4 book ai didi

javascript - TS : Infer literal typing based on chained methods

转载 作者:行者123 更新时间:2023-12-04 07:38:23 26 4
gpt4 key购买 nike

我只剩下一件事可以解锁我的流程并发布我的表单验证库的第一个试用版。
我有以下代码(当然我省略了很多东西,所以它不会变得太大)

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>
所以我得到以下结果
enter image description here
但我需要获得尽可能真实的输入,我的意思是,类似于为 form 定义的模式, 例子
interface HowNeedsToBe {
email: string
age: number | undefined
gender: 'male' | 'female'
color: 'red' | 'blue' | 'green' | undefined
}
我相信逻辑是这样的,在没有 required 的情况下, 放置一个 undefined如果有 oneOf , 将参数替换为 TMixedSchema<T> ,但我不知道如何将这个发回至 MixedSchema<T> ,其实我觉得这个逻辑很乱。
我已经研究了 typescript 中的 map 和泛型,但我承认,当它付诸实践时,没有什么好处。
这是 TS playground如果你想尝试。

最佳答案

从概念上讲,您需要 required()oneOf()缩小范围的方法 T ;这很容易提供类型(尽管您需要 type assertions 以避免编译器错误,因为编译器无法验证您是否确实完成了必要的缩小)。所以required() ,调用 MixedSchema<T> , 应该返回 MixedSchema<Exclude<T, undefined>> (使用 the Exclude utility typeundefined 的任何联合成员中删除 T )。和 oneOf()应该是 generic在元素类型 U arrayOfValues 的元素,并且应该返回一个 MixedSchema<U | Extract<undefined, T>> (如果 Extract 可以是 undefined ,则使用 the T utility type 保留 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使事情复杂化;你想说,例如, StringSchema应该留个 StringSchema打电话后的某种形式 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 中请求(但未实现)的排序。 .你想这样说: MixedSchema<T>required()方法应该返回 this<Exclude<T, undefined>> ,您正在以某种方式对待 this就像一个带有泛型参数的类型。所以如果 thisStringSchema<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;
} */
看起来挺好的!
Playground link to code

关于javascript - TS : Infer literal typing based on chained methods,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67627364/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com