TypeScript의 생성자는 타입이 있는 매개변수를 받는 클래스의 constructor 메서드입니다. 반환 타입 표기는 없습니다. 항상 클래스의 인스턴스를 만듭니다.
constructor(): Product라고 쓰면 오류 TS1093(Type annotation cannot appear on a constructor declaration)입니다. 그 밖에 new가 동작하는 방식은 모두 평범한 JavaScript입니다.
매개변수 속성
위의 패턴(필드를 선언하고, 매개변수를 받아서, 복사하기)은 아주 흔해서 TypeScript에 줄임 문법이 있습니다. 생성자 매개변수 앞에 public, private, protected, readonly를 붙이면 필드가 됩니다.
코드를 생성하는 몇 안 되는 TypeScript 기능 중 하나입니다. 컴파일러가 대입을 대신 써 줍니다.
class Product {
name;
price;
sku;
constructor(name, price, sku) {
this.name = name;
this.price = price;
this.sku = sku;
}
// ...
}
단순히 지워지는 타입이 아니므로 매개변수 속성은 Node의 내장 타입 제거에서 실행되지 않고(node file.ts가 ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX로 실패), erasableSyntaxOnly 컴파일러 옵션은 이를 오류 TS1294로 보고합니다. .ts 파일을 바로 실행하는 프로젝트는 필드를 손으로 씁니다.
선택적 매개변수와 기본값
생성자 매개변수는 함수 매개변수와 같은 규칙을 따릅니다. ?는 선택적으로 만들고, 기본값은 선택적으로 만들면서 값을 채우며, 필수 매개변수가 먼저 옵니다.
매개변수 속성도 기본값을 가질 수 있습니다. 클래스 안에서 color의 타입은 string | undefined이므로, 그것을 쓰는 코드는 먼저 undefined를 확인해야 합니다.
옵션이 많은 생성자라면 긴 위치 인수 목록보다 옵션 객체 하나가 읽기 좋습니다: constructor(opts: { prefix: string; level?: "info" | "debug" }).
필드 초기화 순서
필드 초기값은 생성자 본문보다 먼저 실행되고, 매개변수 속성은 생성자 본문의 시작에서 대입됩니다. 그래서 필드 초기값은 매개변수 속성을 읽을 수 없습니다. TypeScript가 이를 잡습니다.
index.ts(3,18): error TS2729: Property 'size' is used before its initialization.
index.ts(3,30): error TS2729: Property 'size' is used before its initialization.
런타임이라면 초기값은 undefined를 보고 NaN을 만들 것입니다. 대신 생성자 본문에서 값을 계산하세요.
class Grid {
cells: number;
constructor(public size: number) {
this.cells = size * size;
}
}
console.log(new Grid(3).cells); // 9
생성자 오버로드
클래스에는 생성자 구현이 정확히 하나 있지만, 그 위에 오버로드 시그니처를 여러 개 나열할 수 있습니다. 호출하는 쪽은 오버로드만 보고, 구현 시그니처는 그 모두와 호환되어야 합니다.
모양이 두세 개를 넘으면 오버로드는 읽기 어려워집니다. 이름이 설명적인 정적 팩토리 메서드(Color.fromHex("#ff8800"), Color.fromRgb(10, 20, 30))가 더 명확한 경우가 많고, 본문 하나 안에서 타입을 좁힐 필요도 없습니다.
하위 클래스에서 super 호출하기
자체 생성자를 선언하는 파생 클래스는 부모의 인수로 super(...)를 호출해야 하며, this를 건드리기 전에 해야 합니다. 호출을 잊으면 오류 TS2377(Constructors for derived classes must contain a 'super' call)이고, this를 먼저 쓰면 TS17009('super' must be called before accessing 'this' in the constructor of a derived class)입니다. 둘 다 JavaScript 런타임 규칙을 반영합니다.
class Animal {
constructor(public name: string) {}
}
class Dog extends Animal {
tricks: string[];
constructor(name: string, ...tricks: string[]) {
super(name); // must come first
this.tricks = tricks;
}
}
const rex = new Dog("Rex", "sit", "roll");
console.log(rex); // Dog { name: 'Rex', tricks: [ 'sit', 'roll' ] }
생성자를 추가하지 않는 하위 클래스는 같은 매개변수 타입으로 부모의 생성자를 상속합니다. 하위 클래스에 대한 더 자세한 내용은 상속에 있습니다.
private 생성자와 비동기 설정
private 생성자는 클래스 안에서만 호출할 수 있습니다. 밖에서 new를 쓰면 오류 TS2673입니다. 싱글턴을 위해서든, 비동기여야 하는 설정을 위해서든(생성자는 async일 수 없습니다) 호출하는 쪽이 팩토리를 거치게 만드는 방법입니다.
여기서 private는 컴파일 시점의 규칙일 뿐입니다. 출력된 JavaScript에는 평범한 생성자가 있으므로, 평범한 JavaScript 코드는 여전히 new Connection(...)을 호출할 수 있습니다.
생성자 타입
클래스를 값으로 받으려면 매개변수에 생성 시그니처 new (...args) => Instance로 타입을 붙이세요. typeof MyClass도 되지만, 매개변수를 그 클래스 하나의 생성자 시그니처와 정적 멤버에 묶어 버립니다.
ConstructorParameters와 InstanceType은 생성자 타입에서 매개변수 튜플과 인스턴스 타입을 꺼냅니다. 다른 함수 헬퍼와 함께 ReturnType과 Parameters에서 다룹니다.
자주 묻는 질문
TypeScript의 매개변수 속성이란 무엇인가요?
제한자(public, private, protected, readonly)가 붙은 생성자 매개변수는 필드를 선언하고 대입하는 일을 한 번에 합니다. constructor(private name: string) {}는 private name: string을 선언하고 this.name = name을 쓰는 것의 줄임말입니다. 컴파일러가 출력에 그 대입을 생성합니다.
TypeScript 클래스에 생성자가 여러 개 있을 수 있나요?
구현을 여러 개 둘 수는 없습니다. 클래스에는 생성자가 하나지만, 그 위에 오버로드 시그니처를 여러 개 써서 호출하는 쪽이 서로 다른 매개변수 목록을 보게 할 수 있습니다. Color.fromHex()와 Color.fromRgb() 같은 정적 팩토리 메서드가 오버로드보다 더 명확한 경우가 많습니다.
TypeScript에서 생성자를 async로 만들 수 있나요?
아니요. 생성자는 항상 새 인스턴스를 반환하고 절대 프로미스를 반환하지 않으며, async constructor()는 오류 TS1089('async' modifier cannot appear on a constructor declaration)로 거부됩니다. private 생성자와, 비동기 작업을 한 뒤 new를 호출하는 정적 async create() 메서드를 쓰세요.
클래스 생성자를 매개변수로 받을 때 타입은 어떻게 지정하나요?
생성 시그니처 new (name: string) => User를 쓰거나, 그 클래스로 한정한다면 typeof User를 쓰세요. 제네릭 팩토리는 function make<T>(ctor: new () => T): T { return new ctor(); } 모양입니다. ConstructorParameters<typeof User>는 매개변수 목록을 튜플로 줍니다.
"'super' must be called before accessing 'this'" 오류는 왜 나나요?
오류 TS17009입니다. 다른 클래스를 extends하는 클래스에서는 부모 생성자가 객체를 만들므로, super(...)가 실행되기 전에는 this가 존재하지 않습니다. super 호출을 this를 건드리는 모든 줄보다 위로 옮기세요.