如何推断TypeScript中的可选属性?
对于数据库架构,我有以下类型定义:如何推断TypeScript中的可选属性?,typescript,type-inference,Typescript,Type Inference,对于数据库架构,我有以下类型定义: 类型架构={ [K in keyof T]:模式类型 } 接口模式类型{ 可选:布尔值 验证(t:t):布尔值 } 下面是一个实现它的对象: const userSchema={ 姓名:{ 可选:true, 验证(名称:字符串){ 返回name.length>0&&name.length0&&name.lengthoptional设置为true //else->optional设置为false。 //我希望TypeScript在推断类型时可以逆转这一点: /
类型架构={
[K in keyof T]:模式类型
}
接口模式类型{
可选:布尔值
验证(t:t):布尔值
}
下面是一个实现它的对象:
const userSchema={
姓名:{
可选:true,
验证(名称:字符串){
返回name.length>0&&name.length<30
}
}
}
我希望能够根据userSchema
对象推断Schema
中的泛型类型T
。使用TypeScript的推断
关键字,无需花费太多精力即可完成此操作:
type extractType=T扩展模式?X:null
//用户被推断为{name:string}
类型User=extractType
更好的是,我能够间接推断用户
类型:
类模型{
构造函数(私有模式:模式){}
}
//userModel被推断为类型模型
const userModel=新模型(userSchema)
但是,我希望能够将User
推断为{name?:String}
,其中name
是可选的,因为它的optional
属性设置为true。有办法做到这一点吗
一些相关的打字脚本功能
下面是我在处理这个问题时遇到的一些潜在有用的TypeScript特性
用户模式
上的可选
字段被推断为布尔值
,但是可以使用as const
将其推断为布尔值文本
//用户架构推断为
//{name:{可选:true,验证(名称:字符串):boolean}
//而不是
//{名称:{可选:布尔,验证(名称:字符串):布尔}
const userSchema={
姓名:{
可选:作为常量为true,
验证(名称:字符串){
返回name.length>0&&name.length<30
}
}
}
要求这样做并不理想,但我不确定这是可以避免的
extractType
实现中使用like,以更有限的方式定义Schema
:
//下面的界面本质上是这样的:
//如果T[K]是可选属性
//然后->optional设置为true
//else->optional设置为false。
//我希望TypeScript在推断类型时可以逆转这一点:
//如果optional设置为true
//然后->T[K]是可选属性
//else->T[K]是必需的属性
类型架构={
[K in keyof T]:未定义的扩展T[K]
?OptionalSchemaType//与SchemaType相同,但带有可选项:true
:RequiredSchemaType//与SchemaType相同,但可选:false
}
const userModel=new Model(userSchema)//错误!
不幸的是,TypeScript的类型推断不够聪明,无法推断出如此复杂的类型
我不确定我想要实现的目标是否可能,但非常感谢任何帮助或替代方案。:) 首先,您可能需要将
true编写为const
或true编写为true
或类似的内容,因为编译器会倾向于将布尔文本加宽为typeboolean
,除非它们是在编译器要求更窄类型的上下文中声明的。避免显式强制转换的一种可能方法是使用一个helper函数,该函数返回其输入,它希望在这样一个较窄的类型上下文中返回该输入:
const asSchema = <S extends Record<keyof S, { optional: B }>, B extends boolean>(
s: S
) => s;
const userSchema = asSchema({
name: {
optional: true,
validate(name: string) {
return name.length > 0 && name.length < 30
}
},
age: {
optional: false,
validate(age: number) {
return age >= 0 && age < 200;
}
}
});
如果你打算创建很多这样的东西,那么使用helper函数可能是值得的;或者您可以只编写可选:false as false
。无论如何,继续:
我认为您需要直接计算类型,而不是依赖类型推断。直接方法需要显式地写出类型的哪些部分转化为哪些部分。让我们想出一些实用程序类型:
PartialPartial
类型接受一个类型T
及其一组键K
,并返回一个新类型,如T
,但K
索引的所有属性都是可选的。所以,PartialPartial
是{a?:string,b:number}
type PartialPartial<T, K extends keyof T> = Partial<Pick<T, K>> & Omit<T, K> extends
infer O ? { [P in keyof O]: O[P] } : never;
现在是ExtractType
。给定一个类似于T
的模式,首先映射其每个属性,并将第一个参数提取到validate()
方法。然后,获取整个结果,并将其optional
属性为true
的所有属性设置为可选:
type ExtractType<T extends { [K in keyof T]: SchemaType<any> }> = PartialPartial<{
[K in keyof T]: Parameters<T[K]['validate']>[0]
}, KeysMatching<T, { optional: true }>>
看起来不错
如果我们放弃类型推断,这意味着你不能轻易地用
Schema
构建Model
(至少不能用标准的class
语句)。解决这个问题的一种方法是让模式类型S
中的Model
是泛型的,并在需要使用T
的任何地方使用ExtractType
。您甚至可以将T
放在类型定义中,这样您就有了Model
,其中S
是模式类型,T
是提取的类型:
class Model<S extends Record<keyof S, SchemaType<any>>, T extends ExtractType<S> = ExtractType<S>> {
constructor(private schema: S) { }
}
const userModel = new Model(userSchema);
// S is typeof userSchema
// T is {name?: string, age: number}
并测试它:
const myUserModel = new MyModel(userSchema);
/* const myUserModel: MyModel<{
name?: string | undefined;
age: number;
}> */
constmyusermodel=newmymodel(userSchema);
/*常量myUserModel:MyModel*/
在这里您可以看到newmymodel(userSchema)
生成一个MyModel
。这很好,但您会发现自己必须为类的静态/实例端编写冗余接口声明,然后为其分配其他类构造函数。重复可能不值得;这取决于你
好吧,我知道那是很多东西。希望对你有帮助。祝你好运
接口模式{[K in keyof T]:模式类型}
不是有效的TS;大概你的意思是typeschema=…
。修复了!谢谢你指出这一点。非常感谢!这正是我要找的。我不确定这是否是你所尝试的
type UserThing = ExtractType<typeof userSchema>;
/* type UserThing = {
name?: string | undefined;
age: number;
} */
class Model<S extends Record<keyof S, SchemaType<any>>, T extends ExtractType<S> = ExtractType<S>> {
constructor(private schema: S) { }
}
const userModel = new Model(userSchema);
// S is typeof userSchema
// T is {name?: string, age: number}
interface MyModelConstructor {
new <S extends Record<keyof S, SchemaType<any>>>(schema: S): MyModel<ExtractType<S>>;
}
interface MyModel<T> {
something: T;
}
const MyModel = Model as any as MyModelConstructor;
const myUserModel = new MyModel(userSchema);
/* const myUserModel: MyModel<{
name?: string | undefined;
age: number;
}> */