Typegoose를 써보려구요.

## Typegoose Motivation
몽구스타입스크립트를 함께 사용할 때의 일반적인 문제는 몽구스 모델타입스크립트 인터페이스를 둘다 정의해야 된다는 것이다.(동기화) 만약에 모델이 변경되면 타입스크립트 인터페이스도 같이 변경해줘야한다. 이게 꽤~ 번거롭다. 그래서 이Typegoose가 만들어졌다.

Typegoose는 이 동기화 문제를 해결하기 위해서 ES6의 Class와 Typegoose만의 데코레이터를 이용한다.


Decorators

Property decorators

  • @prop: 가장 중요한 데코레이터, 모델 혹은 다큐먼트의 value들을 정의할때 쓰인다.
  • @arrayProp, @mapProp도 있지만 Deprecated 됐으니 쓰지말자.

Class decoraters

  • @modelOptions: 스키마 옵션, 몽구스 그리고 연결을 위해 쓰인다.
  • @index: prop에 정의되지 않은 index를 위해 쓰인다.
  • @plugin: 플러그인을 추가할때 쓰인다. (+ plugin은 prop의 타입을 수정할수 없다.)
  • @queryMethod: 쿼리 메소드를 추가할 때 쓰인다.

Hooks

  • @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[];
}

Methods Definitions

Static Methods

: 몽고DB, 몽구스에 자체적으로 미리 정의된 함수들을 이용할때 static 키워드를 이용한다.

아래의 findBySpeciesstatic인 이유는 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");

Instance Methods

데이터를 조정하는데 쓰이는 메서드를 정의할때는 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");

Hooks

@pre, @post

특정 작업 전, 후에 실행된다.

@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>;
}

좋은 웹페이지 즐겨찾기