TypeScript: 클래스(Class)

이전 글 - TypeScript: Basic Type

클래스는 JavaScript 생태계 속에서도 TypeScript에만 있는 개념이 아니다. CoffeeScript나 ES2015를 사용해봤다면 이미 클래스를 몇 번 쯤은 사용해보았을 것이다. 이 글에서는 ES2015의 클래스를 알고 있다는 전제하에 TypeScript의 클래스의 몇 가지 다른 부분들에 대해서만 설명한다.

프로퍼티 정의

TypeScript의 클래스에서는 선언한 값만 객체의 프로퍼티 값으로 활용할 수 있다. 예를 들면,

1
2
3
4
5
6
class Rect {
constructor(width, height) {
this.width = width;
this.height = height;
}
}

이 코드는 ES2015에서는 잘 돌아가는 코드이다. 또한 클래스의 프로퍼티를 정의하는 일반적인 방법이다. 하지만 이 코드는 TypeScript에서는 Property 'width' does not exist on type 'Rect'. 라는 에러를 뿜으며 돌아가지 않는다. 말 그대로 TypeScript에서는 Rect 클래스에 width 프로퍼티가 정의되지 않은 상태이기 때문이다. TypeScript에서 프로퍼티를 정의하기 위해서는 다음과 같이 써야한다.

1
2
3
4
5
6
7
8
9
class Rect {
width : number;
height: number;

constructor(width: number, height: number) {
this.width = width;
this.height = height;
}
}

클래스 내부에 위와 같이 타입과 함께 사용할 프로퍼티를 선언할 수 있다. 또한 생성자로 넘겨받는 인수도 타입 선언을 해주면 정상적으로 동작한다.

Static 프로퍼티

ES2015에서는 클래스에 static 키워드를 제공하고 있다. 하지만, ES2015의 static은 메소드 전용으로, 프로퍼티에는 활용할 수 없는 키워드다. 다행히도 TypeScript에서는 static 키워드를 프로퍼티에도 활용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Rect {
width : number;
height: number;
static count: number;

constructor(width, height) {
this.width = width;
this.height = height;
Rect.count++;
}
}

new Rect(10, 10);
new Rect(10, 20);
Rect.count; // 2

접근 제한자(Access modifier)

객체지향적으로 JavaScript를 사용하게 되면 늘 남는 아쉬움이 하나 있다. 바로 은닉화다. JavaScript에서는 접근 제한자를 지원하지 않기 때문에 기본적으로 프로퍼티에 대해 접근을 막을 수는 없다. 때문에 private 프로퍼티의 식별자의 맨 앞에 언더스코어(_)를 넣어줌으로써 이 변수가 private으로 사용된다는 컨벤션을 사용하곤 하지만, 역시 외부에서의 접근을 막을 수 없다는 점에서는 동일하다. 이 문제는 ES2015에서도 동일하다. 하지만 TypeScript는 접근 제한자를 제공하기 때문에 이 문제를 해결할 수 있다.

사용법은 간단하다. 프로퍼티 선언시에 접근 제한자로 사용될 수 있는 private, public, protected 같은 키워드를 같이 선언해주면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Rect {
private width : number;
private height: number;

constructor(width, height) {
this.width = width;
this.height = height;
}

getArea(): number {
return this.width * this.height;
}
}

const rect = new Rect(10, 10);
rect.width; // ERROR: Property 'width' is private and only accessible within class 'Rect'.

기본적으로 접근 제한자를 사용하지 않는다면 모든 프로퍼티와 메소드는 public이다.

생성자에서의 활용

생성자에서도 접근 제한자를 사용할 수 있다. 생성자로 넘겨지는 인수에 접근 제한자를 사용하게 되면 해당 클래스에 그 인수의 식별자 이름과 같은 식별자의 프로퍼티가 정의되고 넘겨진 값으로 할당된다. 예를 들면, 위의 코드는 아래와 같이 다시 쓸 수 있다.

1
2
3
4
5
6
7
8
9
10
class Rect {
constructor(private width: number, private height: number) { }

getArea(): number {
return this.width * this.height;
}
}

const rect = new Rect(10, 10);
rect.getArea(); // 100

인자로 넘긴 width, height 값이 묵시적으로 객체 인스턴스 프로퍼티에 할당된 것을 알 수 있다.

Readonly 제한자

TypeScript에는 이외에도 readonly라는 특별한 제한자를 하나 제공한다. 이름에서 쉽게 유추할 수 있듯이 읽기만 가능한 프로퍼티를 선언할 때 사용된다. 쉽게 말해 프로퍼티를 위해 사용할 수 있는 const인 것이다.

readonly는 접근 제한자와 섞어서도 사용할 수 있다. 다음과 같은 문법으로 사용한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
class Rect {
private readonly width : number;
private readonly height: number;

constructor(width, height) {
this.width = width;
this.height = height;
}

set width(width) {
this.width = width; // ERROR: Left-hand side of assignment expression cannot be a constant or a read-only property.
}
}

쉽게 예상할 수 있었겠지만, readonly 프로퍼티에 새 값을 할당하려고 하면 에러를 출력하게 된다.

추상 클래스(Abstract class)

추상 클래스는 추상 메소드(Abstract method) 를 가질 수 있는 클래스다. 추상 메소드란 구현부가 없는 메소드다. 추상 클래스를 상속하는 클래스에서는 반드시 추상 클래스의 추상 메소드를 구현해야 한다. 또한 추상 클래스로는 객체 인스턴스를 생성할 수 없다. 상속용으로만 기능한다.

인터페이스(Interface)를 알고 계신 분이라면 인터페이스와 뭐가 다른지 궁금할 것이다. 인터페이스는 모든 메소드가 추상 메소드이다. 추상 클래스는 추상 메소드만 포함할 수 있는 것이 아니라, 실제 구현이 있는 메소드도 포함할 수 있다. 인터페이스는 이외에도 많은 기능들과 개념을 포함하고 있으므로, 이에 대해서는 다음 글에서 다루려고 한다.

다음 글 - TypeScript: 인터페이스(Interface)