情况:VS.Code中Node.js和Typescript中的猫鼬。
在我从模板继承的代码中,有以下Typescript代码:
import mongoose from "mongoose";
export type UserModel = mongoose.Document & {
firstName: string,
lastName: string
};
const UserSchema = new mongoose.Schema({
firstName: String,
lastName: String
});
export const User = mongoose.model("User", UsersSchema);
以上工作。每当需要调用集合的User
或其他方法时,我都会使用find
。每当需要指定回调参数的类型以使用Intellisense和编译时属性检查时,我都会使用UserModel
。一个例子是:
User.find((err, result: UserModel[])=>
{ console.log(result[0].lastName); }
但是,要确保UserModel
一直在镜像UsersSchema
是一件很麻烦的事情。当有很多属性时,这变得很乏味。
是否有一种更优雅的方式来利用架构直接注释类型?
答案 0 :(得分:1)
助手类型系统:
type SingleValueType<T> = Object extends T ? any :
T extends typeof mongoose.SchemaTypes.Mixed ? any :
T extends typeof Date ? Date :
T extends {(...args: any[]): infer R} ? R :
T extends {new (...args:any[]): infer R} ? R : never;
type ValueType<T> = T extends Array<infer R> ? Array<SingleValueType<R>> : SingleValueType<T>;
type DefaultType<T> = T extends {(...args: any[]): infer R} ? R : T;
type FieldDescriptionType<T> = [ValueType<T>] extends [never] ?
T extends {type: infer R, default: infer D} ? ValueType<R> | DefaultType<D> :
T extends {type: infer R} ? ValueType<R> : never
: ValueType<T>;
type FieldType<T> = [FieldDescriptionType<T>] extends [never] ?
T extends {[index: string]: any} ? docType<T> : never
: FieldDescriptionType<T>;
export type docType<T> = {
[P in keyof T]: FieldType<T[P]>
}
然后,我们可以使用以下最后一种类型:
const userDoc = {
data: mongoose.Schema.Types.Mixed,
firstName: String,
lastName: {type: String},
index: Number,
oAuth: { provider: String, id: String, wrong: '' },
day: Date,
flag: Boolean,
itemNames: {type: [String], default: undefined},
itemIds: {type: [Number]}
};
export type UserModel = mongoose.Document & docType<typeof userDoc>;
const UserSchema = new mongoose.Schema(userDoc);
export const User = mongoose.model("User", UserSchema);
User.find((err, result: UserModel[])=> {
console.log(result[0].data); // any, since it's Mixed in mongoose (would be unknown in TS 3.0)
console.log(result[0].oAuth.provider); // string
result[0].oAuth.wrong = ''; // error, wrong is never
console.log(result[0].lastName); // string
console.log(result[0].day); // Date
console.log(result[0].flag); // boolean
console.log(result[0].itemNames); // string[] | undefined
console.log(result[0].itemIds); // number[]
});
助手类型由以下部分组成。
SingleValueType<T>
是允许我们通过其大写形式(即按对象的基元)或按实例的实数类型来获取实字段类型的类型;机制基于these officially available types.,此类型本身分为以下几部分:
首先,我们对Mixed
模式类型有特殊的对待,因为必须立即将其转换为any
(在TS 2.9中)或unknown
(在TS 3.0中) 。它可能以两种不同的方式出现:要么显式声明,要么作为空对象文字;最后一种情况必须在这里处理,因此我们不会将其与subdocument属性混合使用。
接下来,我们对类型Date
进行了特殊处理,因为new Date()
给了我们
Date
,但是与大多数其他类不同,Date()
只是给了我们string
。所以,我们
只是明确说明它应该是什么。
接下来,如果用作参数的类型T
是一个函数(即具有
调用签名),结果必须是此函数的返回类型。
这是从string
获取String
,从number
获取Number
的方法,
等等。
接下来,如果T
是构造函数类型(即具有new
签名),则
结果必须是此构造函数的返回类型,即实例
类。这可以用来存储任意复杂的结构。
最后,如果不能使用以前的任何东西,即T
既不是
函数或构造函数类型,我们将类型设置为never
,这是扩展never
本身的唯一类型。
类型ValueType
和DefaultType
使用不同的转换,因为它们的设置不同。 ValueType
可以将其参数作为类型本身(传递给SingleValueType
)或作为包含此类型的数组来处理(因此它也为我们提供了一个数组)。 DefaultType
仅使用给定函数的返回类型,或者给定类型本身(如果不是函数)。
FieldType<T>
是直接使用ValueType<T>
(如果可以应用,即如果不是never
的类型),或者执行两项附加检查的类型:
字段可以使用type
元素(也可以使用默认值)显式声明其类型。如果
就是这种情况,我们在此元素上调用ValueType
。这是
分离ValueType
和FieldType
的原因,因为类型不能
直接递归。
字段本身可以是架构对象。在这种情况下,我们只需要
就这样,然后调用下面定义的docType<T>
。
然后,它以通用类型docType<T>
使用,该类型仅将T
的每个属性映射到相应的FieldType
s。
最后,在真实代码中,我们将架构存储到常量变量中;基于该变量类型的docType
将是文档类型。