[TS] Cannot access '_' before initialization 오류 import type으로 해결하기

"Cannot access '_' before initialization" 오류는 말 그대로 무언가를 초기화하기 전에 사용하는 경우 발생합니다.
순환참조 문제
대표적으로는 클래스 간 의존성을 잘못 설계한 경우입니다. 다음과 같은 예를 생각해 볼 수 있겠습니다.

Hyundai
클래스가 Car를 상속하고, Car
클래스가 다시 Vehicle
클래스를 상속하고, Vehicle
클래스가 Hyundai
클래스를 참조하는 경우입니다. 순환참조(circular reference) 문제라고도 합니다.
이때 만약 Hyundai
객체를 생성하려고 한다면(new Hyundai()
) "Cannot access 'something' before initialization" 오류가 발생할 겁니다.
런타임에 Hyundai
객체를 생성하기 위해서는 먼저 생성할 Hyundai
클래스를 올바르게 정의해야 합니다. 그런데 Hyundai
클래스를 정의하기 위해서는 먼저 Car
를 Import해야 하죠. Car
도 마찬가지입니다. Car
클래스를 정의하기 위해서는 Vehicle
클래스를 Import해야 합니다. 그리고 여기서는 다시 Vehicle
클래스가 Hyundai
클래스를 import하고 있습니다.
예제가 단순하기 때문에 얼핏 그다지 벌어지지 않을 실수 같아 보입니다. 하지만 분명 클래스 사이 관계가 복잡한 애플리케이션을 구현하다 보면 종종 생기는 일입니다.
import type 구문 활용법
물론 클래스 사이 관계를 다시 설계할 수도 있지만 그게 어려울 때도 있습니다. 이러한 경우 힌트는 런타임에 타입 정보를 제거하는 타입스크립트 특성을 활용하는 방법입니다.
만약 Vehicle
클래스가 Hyundai
클래스를 다음과 같이 타입 선언 용도로 사용하고 있다고 가정해 봅시다.
import { Hyundai } from "./hyundai";
export class Vehicle {
hyundai?: Hyundai;
}
이러한 경우 import type
구문을 활용해서 런타임에 Hyundai
에 대한 의존성을 없앨 수 있습니다. 그러면 오류도 사라지게 됩니다. 더 이상 런타임에 Vehicle
을 정의하기 위해 Hyundai
를 불러올 필요가 없어지기 때문입니다.
import type { Hyundai } from "./hyundai";
export class Vehicle {
hyundai?: Hyundai; // 런타임에 Hyundai 타입이 없어집니다.
}
거듭 말하지만 여기서 import type
구문을 활용할 수 있는 이유는 Hyundai
클래스를 타입 선언 용도로 사용하고 있기 때문입니다. 만약 다음과 같이 상속하하는 클래스를 import type
구문으로 불러온다면 어떻게 될까요?
import type { Vehicle } from "./vehicle";
export class Car extends Vehicle {}
안타깝게도 이런 식으로는 순환참조 문제를 해결할 수 없습니다. 아예 컴파일되지 않는 코드입니다. 다음과 같은 컴파일 오류가 뜰 겁니다.
'Vehicle' cannot be used as a value because it was imported using 'import type'.ts(1361)
import type
구문을 이용해 불러왔기 때문에 타입으로만 사용할 수 있지 값으로는 사용할 수 없다는 이야기입니다. extends로 상속하기 위해서는 클래스를 값으로 사용해야 합니다. 상속이란 타입뿐만 아니라 구현까지도 물려받아야 하기 때문입니다.
한발짝 나아가서 이러한 특성을 이해했다면 implements
키워드를 사용하는 경우에는 import type
을 이용해 불러온 클래스도 상속이 가능하다는 점도 추론할 수 있습니다. implements
로 클래스를 상속하는 경우는 해당 클래스를 인터페이스처럼 사용하기 때문입니다. 그리고 타입스크립트에서 인터페이스는 런타임 때 제거되죠.