목차
TypeScript 클래스(Class)
TypeScript의 클래스는 JavaScript class 문법에 타입 검사를 더한다. 아래 예시는 메서드, 필드, 읽기 전용 속성, 인터페이스 구현을 차례로 살펴본다. 타입 표기는 컴파일 후 JavaScript 실행 시에는 사라진다.
생성자와 메서드
class Greeter {
constructor(message: string) {
console.log(`construct! ${message}`);
}
greet(name: string): void {
console.log(`${name}, Hello TypeScript!`);
}
}
new Greeter('hhh').greet('홍길동');
constructor는 new로 인스턴스를 만들 때 실행된다. greet는 메서드이고, 매개변수 name은 문자열이어야 한다.
클래스 필드와 초기화
타입 검사를 켜면 사용하려는 필드는 클래스 안에 선언하고, 초기값 또는 생성자에서 값을 할당한다.
class FieldTrip {
destination: string;
constructor(destination: string) {
this.destination = destination;
}
}
const trip = new FieldTrip('Seoul');
console.log(trip.destination);
다음처럼 선언할 때 바로 초기화할 수도 있다. next()가 배열을 새로 만들어 남은 항목을 보관하므로 호출 전후의 순서를 이해하기 쉽다.
class ActivitiesQueue {
private pending: string[] = [];
init(items: string[]): void {
this.pending = [...items];
}
next(): string | undefined {
const [item, ...rest] = this.pending;
this.pending = rest;
return item;
}
}
const queue = new ActivitiesQueue();
queue.init(['eat', 'sleep', 'learn']);
console.log(queue.next()); // eat
원래 예제의 pending!: string[]에 붙은 !는 확정 할당 단언(definite assignment assertion)이다. 생성자 밖에서 반드시 초기화하겠다고 타입 검사기에 알릴 뿐, 배열을 만들어 주지 않는다. init() 전에 pending.pop()을 호출하면 런타임 오류가 날 수 있다. 확실한 이유가 없으면 위처럼 초기값을 주는 편이 안전하다. 자세한 조건은 TypeScript 클래스 문서에 설명돼 있다.
메서드와 함수 값 필드
class WithMethod {
myMethod(): void {
console.log('method');
}
taskParams = (input: boolean): string => input ? 'YES' : 'NO';
}
const instance = new WithMethod();
console.log(instance.taskParams(true));
myMethod는 프로토타입 메서드다. taskParams는 인스턴스를 만들 때 생성되는 함수 값 필드다. 화살표 함수 필드는 this를 인스턴스에 묶어 쓰기 편하지만, 인스턴스마다 함수가 만들어진다는 차이가 있다.
readonly와 인터페이스
class Quote {
readonly text: string;
constructor(text: string) {
this.text = text;
}
}
const quote = new Quote('Hello');
console.log(quote.text);
// quote.text = 'Bye'; // 타입 오류: 생성자 밖에서 재할당 불가
interface Learner {
name: string;
study(hours: number): void;
}
class Student implements Learner {
constructor(public name: string) {}
study(hours: number): void {
console.log(`${this.name}: ${hours}시간 학습`);
}
}
new Student('홍길동').study(2);
readonly는 필드에 다시 값을 할당하는 것을 타입 검사에서 막는다. 객체 전체를 깊게 불변으로 만들거나 런타임 변경을 자동으로 막지 않는다. implements는 클래스가 인터페이스에서 요구한 필드와 메서드를 갖는지 검사한다. constructor(public name: string)은 매개변수와 인스턴스 필드를 함께 선언하는 TypeScript 문법이다. 공식 클래스 가이드는 이 동작과 사용 범위를 설명한다.
접근 제한은 타입 검사와 실행을 나누어 보기
TypeScript의 private는 일반적으로 컴파일 시 접근 검사다. JavaScript로 변환된 뒤에도 언어 차원에서 접근을 막는 #privateField와는 다르다. 외부 JavaScript 코드나 타입 단언으로 접근 제한을 우회할 수 있으므로 비밀 값의 보안 경계로만 믿지 않는다.
class Account {
#balance: number;
constructor(initial: number) {
if (!Number.isFinite(initial) || initial < 0) throw new RangeError("초기 잔액");
this.#balance = initial;
}
balance(): number { return this.#balance; }
deposit(amount: number): void {
if (!Number.isFinite(amount) || amount <= 0) throw new RangeError("입금액");
this.#balance += amount;
}
}
const account = new Account(10);
account.deposit(5);
console.log(account.balance()); // 15
// account.#balance; // 클래스 외부에서는 문법 오류
#balance는 JavaScript의 비공개 필드다. 타입 선언뿐 아니라 실행 중 접근도 제한한다. 위 검증은 음수·NaN을 막기 위한 예시이며 실제 금액이라면 부동소수점의 정밀도도 고려해야 한다. 단위를 정수 최소 화폐 단위 또는 별도 금액 타입으로 표현할 수 있다. 공개 API가 클래스의 내부 필드를 직접 만지게 하기보다 메서드로 허용된 상태 변경을 표현하면 유지보수가 쉽다.