상속
TypeGraphQL
에서는 extends
키워드를 통해 기존 타입
을 상속받아 사용할 수 있습니다. 여기서는 아래의 3가지 관점에서 상속을 활용하는 방법을 설명하겠습니다.
주요 관점:
- 베이스 타입이
일반 클래스
인 경우 - 베이스 타입이
추상 클래스
인 경우 - 오버라이딩된 필드는 어떻게 되나?
일반 클래스 상속
위에서 이미 설명하였듯이, 평범하게 extends
키워드를 사용하면 기존 타입을 확장할 수 있습니다. 예를 들어, 아래와 같이 2차원 좌표
를 표현하는 ObjectType
을 정의하면.
@ObjectType()
class Point2D {
@Field(() => Int)
x!: number;
@Field(() => Int)
y!: number;
}
상속을 사용하여 3차원 좌표
로 쉽게 확장할 수 있습니다.
@ObjectType()
class Point3D extends Point2D {
@Field(() => Int)
z!: number;
}
위와 같은 방식으로 아래의 타입들을 확장시킬 수 있습니다.
ObjectType
InputType
ArgsType
Resolver
단, 같은 타입
에 대해서만 상속할 수 있다는 것을 기억해주세요. 다른 타입
을 상속해도 문법적인 에러는 없지만, 스키마를 빌드할 때 에러가 발생하게 됩니다.
추상 클래스 상속
추상 클래스는 일반적으로 ObjectType
에만 사용되기 때문에, 다른 타입에 대해서는 생각하지 않겠습니다. 즉, ObjectType
에 대해서만 생각하겠습니다.
추상 메서드를 통해 정수형 배열
을 매핑하는 오브젝트 타입을 정의하겠습니다. 단, TypeScript
에서는 아직 AbstractMethod
에 데코레이터를 사용할 수 없기 때문에, 추상 메서드가 아닌 함수를 별도로 만들어 사용해야 합니다.
@ObjectType()
abstract class DataMapper {
protected items: number[] = [];
//
// 자식 클래스마다 각기다른 로직을 정의.
// 추상 메소드에는 데코레이터를 사용할 수 없음.
abstract map(): number[];
//
// 추상 메서드는 데코레이터를 사용할 수 없으므로,
// 추상 메서드가 아닌 함수를 프록시로 세워야 함.
@Field(() => [Float])
getMapped(): number[] {
return this.map();
}
}
이제 DataMapper
를 상속받아 각 배열의 값을 2배로 늘리는 TwiceMapper
와, 각 배열의 값을 절반으로 낮추는 HalfMapper
를 구현합니다. getMapped() 필드
는 부모 클래스에서 상속받기 때문에 따로 적어주지 않아도 괜찮습니다.
@ObjectType()
class TwiceMapper extends DataMapper {
constructor() {
super();
this.items = [1, 2, 3, 4, 5];
}
map() {
return this.items.map((v) => v * 2);
}
}
@ObjectType()
class HalfMapper extends DataMapper {
constructor() {
super();
this.items = [1, 2, 3, 4, 5];
}
map() {
return this.items.map((v) => v * 0.5);
}
}
오버라이딩
만약 부모에게 상속받은 Field
를 오버라이딩하면 어떻게 될까요? 먼저, 이항 연산자
와 같은 역할을 하는 ObjectType
을 정의하겠습니다.
@ObjectType()
class BinaryOperatorObject {
@Field(() => String)
operatorName(): string {
throw new Error("'operatorName()' not implemented.");
}
@Field(() => Float)
exec(
@Arg("a", () => Int) a: number,
@Arg("b", () => Int) b: number
): number {
throw new Error("'exec()' not implemented.");
}
}
위의 결과로 아래의 스키마가 생성됩니다. exec
가 2개의 인자를 요구하는 것에 주목해주세요.
type BinaryOperatorObject {
operatorName: String!
exec(a: Int!, b: Int!): Float!
}
단순 오버라이딩
이제 BinaryOperatorObject
를 상속받아 2개의 정수의 합을 구하는 AdderObject
를 만들어보겠습니다.
@ObjectType()
class AdderObject extends BinaryOperatorObject {
operatorName(): string {
return "add";
}
exec(a: number, b: number): number {
return a + b;
}
}
각각의 필드에 데코레이터가 사용되지 않은것에 주목해주세요. 오버라이딩된 필드는 부모객체에서 데코레이터 정보를 가져오기 때문에, 각각의 데코레이터 정보가 손상되지 않습니다. 즉, 아래와 같이 부모와 동일한 스키마가 생성됩니다.
type AdderObject {
operatorName: String!
exec(a: Int!, b: Int!): Float!
}
데코레이터 오버라이딩
이제 BinaryOperatorObject
를 상속받아 2개의 정수의 차를 구하는 SubtractorObject
를 만들어보겠습니다. 이 때, exec
에 데코레이터가 새로 정의된 것에 주목해주세요.
@ObjectType()
class SubtractorObject extends BinaryOperatorObject {
operatorName(): string {
return "subtract";
}
//
// 잘못된 사용법.
// 기존의 데코레이터 정보를 가져올 수 없음.
@Field(() => Float)
exec(a: number, b: number): number {
return a - b;
}
}
오버라이딩된 필드에 데코레이터를 적용하면, 기존의 부모 객체에서 데코레이터 정보를 가져올 수 없게 됩니다. 즉, 반환형을 제외한 모든 정보가 손실되므로 Arg
에 대한 정보가 사라지게 됩니다. 결과 스키마를 보면 바로 파악할 수 있습니다.
type SubtractorObject {
operatorName: String!
exec: Float!
}
만약 부모가 사용한 정의를 그대로 따라야 한다면, 자식에서는 데코레이터를 명시적으로 다시 적지 말아야 합니다.
예제 다운로드
이 포스팅에 사용된 전체 코드는 여기에서 확인할 수 있습니다.
'# GraphQL > TypeGraphQL' 카테고리의 다른 글
[TypeGraphQL] @EnumType (0) | 2020.06.18 |
---|---|
[TypeGraphQL] Scalar (0) | 2020.06.14 |
[TypeGraphQL] @InputType, @ArgsType (0) | 2020.06.13 |
[TypeGraphQL] 기본 자료형과 @ObjectType (0) | 2020.06.08 |
ApolloServer + TypeScript + TypeGraphQL 조합으로 GraphQL 서버 시작하기 (0) | 2020.06.07 |