## Typegoose Motivation
몽구스
와 타입스크립트
를 함께 사용할 때의 일반적인 문제는 몽구스 모델
과 타입스크립트 인터페이스
를 둘다 정의해야 된다는 것이다.(동기화) 만약에 모델이 변경되면 타입스크립트 인터페이스도 같이 변경해줘야한다. 이게 꽤~ 번거롭다. 그래서 이Typegoose
가 만들어졌다.
Typegoose는 이 동기화 문제를 해결하기 위해서 ES6의 Class와 Typegoose만의 데코레이터를 이용한다.
@prop
: 가장 중요한 데코레이터, 모델 혹은 다큐먼트의 value들을 정의할때 쓰인다.@arrayProp
, @mapProp
도 있지만 Deprecated 됐으니 쓰지말자.@modelOptions
: 스키마 옵션, 몽구스 그리고 연결을 위해 쓰인다.@index
: prop에 정의되지 않은 index를 위해 쓰인다.@plugin
: 플러그인을 추가할때 쓰인다. (+ plugin은 prop의 타입을 수정할수 없다.)@queryMethod
: 쿼리 메소드를 추가할 때 쓰인다.@pre
: 특정 함수의 실행 전에 실행된다.@post
: 특정 함수의 실행 완료 뒤에 실행된다.예제 1_ Car Model
interface Car {
model?: string;
}
const CarModel = mongoose.model('Car', {
model: string,
});
=> 아래처럼 간단해진다.
class Car {
@prop()
public model?: string;
}
예제 2_User Model
interface Car {
model?: string;
}
interface Job {
title?: string;
position?: string;
}
interface User {
name?: string;
age!: number;
job?: Job;
car?: Car | string;
preferences?: string[];
}
const CarModel = mongoose.model('Car', {
model: string,
});
const UserModel = mongoose.model('User', {
name: String,
age: { type: Number, required: true },
job: {
title: String;
position: String;
},
car: { type: Schema.Types.ObjectId, ref: 'Car' },
preferences: [{ type: String }]
});
=> 아래코드처럼 간편해진다.
class Job {
@prop()
public title?: string;
@prop()
public position?: string;
}
class Car {
@prop()
public model?: string;
}
class User {
@prop()
public name?: string;
@prop({ required: true })
public age!: number;
@prop()
public job?: Job;
@prop({ ref: () => Car }) // notes here !!
public car?: Ref<Car>;
@prop({ type: () => [String] })
public preferences?: string[];
}
: 몽고DB, 몽구스에 자체적으로 미리 정의된 함수들을 이용할때 static
키워드를 이용한다.
아래의
findBySpecies
가 static인 이유는find()
메서드가 몽구스에 미리 정의되어 있는 메서드이기 때문이다.
class KittenClass {
@prop()
public name?: string;
@prop()
public species?: string;
public static async findBySpecies(this: ReturnModelType<typeof KittenClass>, species: string) {
return this.find({ species }).exec(); // !! NOTE HERE !!
}
}
const KittenModel = getModelForClass(KittenClass);
const docs = await KittenModel.findBySpecies("SomeSpecies");
데이터를 조정하는데 쓰이는 메서드를 정의할때는 static
키워드를 붙이지 않는다.
setSpeciesAndSave() 메서드에는 static이 붙어있지 않다.!
class KittenClass {
@prop()
public name?: string;
@prop()
public species?: string;
public async setSpeciesAndSave(this: DocumentType<KittenClass>, species: string) { // NOTE here
this.species = species;
return await this.save();
}
}
const KittenModel = getModelForClass(KittenClass);
const doc = new KittenModel({ name: "SomeCat", species: "SomeSpecies" });
await doc.setSpeciesAndSave("SomeOtherSpecies");
특정 작업 전, 후에 실행된다.
@pre<KittenClass>('save', function() { // 밑의 setSpeciesAndSave메서드안의 save() 함수가 실행되기전에 실행된다.
this.isKitten = this.age < 1
})
@post<KittenClass>('save', (kitten) => { // .save() 함수 실행후 실행된다.
console.log(kitten.isKitten ? "We have a kitten here." : "We have a big kitty here.")
})
class KittenClass {
public async setSpeciesAndSave(this: DocumentType<KittenClass>, species: string) {
this.species = species;
return await this.save(); // save()
}
}
arrow function을 쓸때 당신이 원하는 this값이 아닐수 있으니 주의하길 바란다.
클래스를 타입으로 이용할때는 allow function을 추천한다.
class Nested {
@prop()
public someNestedProperty: string;
}
// Recommended first fix:
class Main {
@prop({ ref: () => Nested }) // since 7.1 arrow functions can be used to defer getting the type
public nested: Ref<Nested>;
}
// Not recommended workaround (hardcoding model name):
class Main {
@prop({ ref: 'Nested' }) // since 7.0 it is recommended to use "ref: getName(Class)" to dynamically get the name
public nested: Ref<Nested>;
}