统一模型和架构

时间:2018-07-05 10:23:29

标签: typescript mongoose typescript-typings

情况: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是一件很麻烦的事情。当有很多属性时,这变得很乏味。

是否有一种更优雅的方式来利用架构直接注释类型?

1 个答案:

答案 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本身的唯一类型。

类型ValueTypeDefaultType使用不同的转换,因为它们的设置不同。 ValueType可以将其参数作为类型本身(传递给SingleValueType)或作为包含此类型的数组来处理(因此它也为我们提供了一个数组)。 DefaultType仅使用给定函数的返回类型,或者给定类型本身(如果不是函数)。

FieldType<T>是直接使用ValueType<T>(如果可以应用,即如果不是never的类型),或者执行两项附加检查的类型:

  • 字段可以使用type元素(也可以使用默认值)显式声明其类型。如果 就是这种情况,我们在此元素上调用ValueType。这是 分离ValueTypeFieldType的原因,因为类型不能 直接递归。

  • 字段本身可以是架构对象。在这种情况下,我们只需要 就这样,然后调用下面定义的docType<T>

然后,它以通用类型docType<T>使用,该类型仅将T的每个属性映射到相应的FieldType s。

最后,在真实代码中,我们将架构存储到常量变量中;基于该变量类型的docType将是文档类型。