使用TypeScript +猫鼬时,如何减少样板代码的数量

时间:2019-02-28 10:43:05

标签: javascript node.js mongodb typescript mongoose

假设我有两个实体:车辆关税。它们是一对多的关系(车辆可以有很多关税)。

这是我对对象的基本类型定义,我想使用:

interface Tariff {
    id: string;         // <-- ID of entity
    createdAt: Date;    // <-- some meta data fields for sorting and etc.

    days: number;       // <-- userful info
    price: number;      // <-
}

interface Vehicle {
    id: string;          // <-- ID of entity
    createdAt: Date;     // <-- some meta data fields for sorting and etc.

    model: string;       // <-- userful info
    year?: string;       // <-
    seatsCount?: number; // <-
    tariffs: Tariff[];   // <-- array of tariffs
}

看起来很棒。不,我们需要创建数据库模型。首先让我们定义猫鼬模式:

import mongoose from "mongoose";

const vehicleSchema = new mongoose.Schema({
    createdAt: {type: Date, required: true, default: () => new Date()},

    model: {type: String, required: true},
    year: String,
    seatsCount: Number,
});

太棒了。我决定不将关税放入车辆文件中,它们将单独存储。

接下来,我们需要创建猫鼬模型。但是我们需要提供将mongoose.Document扩展到mongoose.model的通用类型。这是代码复制开始的地方:

// First we need to define TypeScript schema equalent of mongoose schema:
interface VehicleSchema {
    createdAt: Date;

    model: string;
    year?: string;
    seatsCount?: number;
}

// Then we need to defined a new type of out vehicle models:
type VehicleModel = VehicleSchema && mongoose.Document;

// And finaly we can create model:
const VehicleModel = mongoose.model<VehicleModel>("Car", carSchema);

// Look, here is a type named "VehicleModel" and a constant named "VehicleModel"


现在我们可以编写用于CRUD操作的函数了:

namespace vehicle {
    export const create = async (form: VehicleCreateForm): Promise<Vehicle> => {
        const fields = toDb(form);

        // assume form is prevalidated
        let dbVehicle = new VehicleModel(form);
        dbVehicle = await model.save();

        // tariff - is the separate module, like this one,  
        // createAllFor methods retuns Tariff[]
        const tariffs = await tariff.createAllFor(dbVehicle.id, form.tariffs);

        return fromDb(dbVehicle, tariffs);
    }

    //...

    // export const update = ...
    // export const list = ...
    // export const get = ...    
    // etc..
}

在此我们引入一种额外的类型:VehicleCreateForm,它描述了创建载具所需的所有字段:

interface VehicleCreateForm {
    model: string;
    year?: string;
    seatsCount?: number;
    tariffs: TariffCreateFields[]; // <-- here is another one special type
}

interface TariffCreateFields {
    days: number;
    price: number;
}

我们还需要定义两个函数:toDb以防止出现某些字段,例如tariffs被传递给模型。然后fromDb将模型“转换”为Vehicle实体,删除或转换某些字段:

const u = <T>(value: T | undefined | null): (T | undefined) => value === null ? undefined : value;

const toDb = (f: VehicleCreateForm): VehicleCreateFields => ({
    model: f.model,
    year: f.year,
    seatsCount: f.seatsCount,
});

const fromDb = (m: VehicleModel, tariffs: Tariff[]): Vehicle => ({
    id: m.id,
    createdAt: m.createdAt,

    model: m.model,
    year: u(m.year),
    tariffs,
});

而且,yaaa,我们还需要一种额外的类型:VehicleCreateFields。我们要传递给模型构造函数的字段。

interface VehicleCreateFields {
    model: string;
    year?: string;
    seatsCount?: number;
}

似乎完成了。

我们还需要定义类似于tariff的{​​{1}}命名空间。所有类型,并且也会重复:vehicleTariffSchemaTariffModelTariffCreateForm

这将在每个新实体中发生。在当前情况下,我可以避免创建TariffCreateDocument类型,而可以使用VehicleUpdateFields进行创建和更新。可能还有VehicleCreateFields等。

如何减少重复代码的数量。您如何处理打字稿/猫鼬?

当然,我可以将公共字段提取到“公共chunck”接口,但是我不知道如何命名它们。

0 个答案:

没有答案