참고: 이 일지는 노션으로부터 옮겨진 레거시 게시물입니다.
개요
자식 클래스의 생성자 함수에서 호출하는 super()는 부모 클래스의 생성자에서 실행되지 않습니다.
class MotherClass {
public constructor() {
//여러가지 초기화 과정
this.init();
}
//이 함수는 외부에서도 호출됩니다.
public init(): void {
console.log("init called in mother class");
}
}
class ChildClass extends MotherClass {
private readonly field1;
public constructor(field1: string) {
super();
//여러가지 초기화 과정
this.field1 = field1;
this.init();
}
public init(): void {
console.log("field is " + this.field1);
}
}
new ChildClass("'hello world!'");
//field is undefined
//field is 'hello world!'
자식 클래스를 생성했을 때,
생성자 함수는 super()를 호출하고 부모 클래스의 생성자 함수에서 여러 초기화 과정을 거친 후 부모의 init()를 호출하고 부모 클래스의 생성자 함수를 마친 후 여러 가지 초기화 과정을 거친 후에 자신의 init()를 호출해서 field1을 사용하는 걸 기대했습니다.
하지만 실제로는 자식의 init()가 두 번 호출되었습니다. 심지어 첫번째는 field1가 undefined인 상태였습니다. 이것은 super()를 타고 부모 생성자 함수에 올라가서 초기화를 거치고 init()를 호출하는 것이 실상은
//in ChildClass#constructor
{
//부모의 여러가지 초기화 과정
this.init();
//자식의 여러가지 초기화 과정
this.field1 = field1;
this.init();
}
즉, super() 의 실행 위치가 MotherClass가 아닌 ChildClass였습니다. field1가 초기화되기 전에 this.init()를 호출하여 field1을 참조하거나 사용했으니 예상치 못한 결과가 생길 것입니다.
타입 스크립트는 이것을 예측할 수 없었습니다.
해결책 후보
생각할 수 있는 해결책들
1. 파라미터 프로퍼티 사용하기
타입 스크립트에선 dart의 this 매개변수와 비슷하게 생성자 파라미터에 직접 필드를 접어 넣을 수 있습니다. 생성자는 별도의 초기화 과정(this.field = field)을 작성할 필요 없이 자동으로 초기화해줍니다. 이는 초기화할 필드 수가 많을수록 유용합니다. 다만 optional bag와 같이 named paramter의 대체용으로 쓰던 객체 매개변수와 상충되는 문법이라 다시 순서에 의존하게 됩니다.
파라미터 프로퍼티는 위치상 생성자 함수의 가장 윗부분에 있기에 가장 먼저 초기화되어 super() 안에서도 초기화된 field1을 사용할 것 같지만, 실제로 테스트해보면 그렇지 않음을 알 수 있습니다.
typescript playground에서 확인할 수 있습니다.
이것은 아래의 해결책 후보(2)처럼 문법 위반이기 때문입니다. 타입스크립트 뿐만이 아니라 자바에서도 super 앞에서의 this 참조를 금지하는 것을 보면 super() 실행에 영향을 줄 수 있기 때문에 금지하는 것 같아 보이지만 나중에 더 알아봐야겠습니다.
2. super 이전에 초기화
문법 오류: 핸드북에서 super 호출 후에 this를 참조하라고 명시되어있습니다.
https://www.typescriptlang.org/docs/handbook/2/classes.html#super-calls
3. init와 constructor를 병합 또는 분리
init가 constructor에서 독립해있던 이유는 외부의 참조때문이였습니다. 즉, 범용적이지 않지만 이 문제만을 위한 해결책으로 전 constructor에서 init를 호출하지 않고, 팩토리 메서드에서 인스턴스를 만든 후 init를 호출하는 방식으로 바꿨습니다. 이런 분리의 방법 외에도 constructor에 init의 코드를 복붙 하고 init를 호출하지 않는 병합의 방법도 있습니다.
'잡다한거' 카테고리의 다른 글
Symbol.toPrimitive는 문자합을 default로 인식한다. (0) | 2022.12.05 |
---|---|
클래스 타입 강제 변환은 메서드를 보장하지 않는다. (0) | 2022.12.05 |
Next.js에서 ./와 /는 다르다 (0) | 2022.12.05 |