- 타입스크립트 클래스
- 타입스크립트 관련 기능 - 클래스 멤버, 액세스 한정자
- 필수 또는 선택적 매개 변수
- ES6 기능 확장
- 필요에 따라 모든 주요 브라우저 및 플랫폼에서 작동하는 자바스크립트로 컴파일 가능
타입스크립트 클래스
클래스 개념
(예) 자동차를 빌드한다고 할 때
- Car 클래스는 청사진
- 자동차의 특성 - 제조사, 색상, 문 개수 등
- 자동차의 동작 - 가속, 제동, 회전
- 자동차 빌드를 위한 계획일 뿐, 실제 개체가 되려면 Car 클래스에서 Car 인스턴스를 빌드해야 한다.
- Car 클래스를 사용해 고유한 특성이 있는 새로운 Car 객체를 원하는 수만큼 만들 수 있다.
- Car 클래스를 확장해서 ElectricCar 같은 클래스를 만들 수도 있다. 이 클래스는 Car의 모든 특성과 동작을 포함하지만 주행거리나 충전 옵션 같은 고유한 특성과 동작을 가질 수 있다.

- 클래스는 개체의 데이터를 캡슐화한다 → 데이터와 동작은 클래스에 포함되지만 개체를 사용하는 사람에게 데이터와 동작의 세부 정보를 숨길 수 있다.
- (예) Car 개체의 turn 메서드를 호출하는 경우 자동차 핸들의 작동 원리를 정확하게 알 필요 없고 명령하면 자동차가 왼쪽이나 오른쪽으로 회전한다는 것만 알고 있으면 된다.
- 클래스의 모든 특성과 동작이 속성과 메서드를 통해서만 공개되는 블랙박스 역할을 하므로 코드 작성자가 할 수 있는 일이 제한된다.
클래스 구성 요소
속성(필드)
- 개체의 데이터(또는 특성). 속성은 설정하거나 반환할 수 있는 개체의 본질적 특성
생성자(constructor)
- 클래스를 기반으로 개체를 만들고 초기화하는데 사용하는 특수 함수.
- 클래스의 새 인스턴스를 만들면 생성자는 클래스 모양을 사용해 새 개체를 만들고 이 개체에 전달된 값을 사용해 초기화한다.
접근자 (accessor)
- 속성 값을 get하거나 set할 때 사용하는 함수 형식.
- set 접근자를 생략하면 읽기 전용 속성이 됨.
- get 접근자를 생략하면 액세스 불가 속성이 됨. (액세스를 시도하면 undefined가 반환됨)
메서드
- 개체가 할 수 있는 동작이나 작업을 정의한 함수
- 메서드를 호출하여 개체의 동작을 호출 할 수 있다.
- 클래스 안에서만 액세스할 수 있고
- 클래스의 다른 메서드에서 호출하는 메서드를 정의할 수도 있다.
모든 구성 요소가 필요하지는 않다.
유틸리티 개체의 경우, 메서드와 생성자만 필요하거나 데이터 관리를 위한 속성만 필요할 수도 있다.
연습 - Car 클래스 만들기
- 클래스를 만들기 위해서는 constructor, 접근자, 메서드 등을 정의한다.
- 클래스 이름을 파스칼 표기법 (시작 글자를 대문자로)
class Car {
// properties
// Constructor
// Accessors
// Methods
}
클래스 속성 선언
- 클래스 속성은 초기화될 때 개체에 전달하는 원시 데이터라고 생각하면 됨.
- Car 클래스 속성: 모든 자동차에 적용되는 속성 (제조사, 색상, 문 개수 등)
class Car {
// properties
_make: string;
_color: string;
_doors: number;
// Constructor
// Accessors
// Methods
}
클래스 생성자(constructor) 정의
클래스의 두 가지 형식
- 인스턴스 형식 : 클래스의 인스턴스에 포함되는 멤버를 정의
- constructor 형식 : 클래스 함수의 멤버 정의. 클래스의 정적 멤버를 포함하기 때문에 ‘정적 측면’ 형식이라고도 함.
- constructor 사용하면 클래스트를 간소화하고, 클래스를 사용할 때 보다 쉽게 관리할 수 있다.
constructor 함수의 구성
- constructor 키워드
- 새 인스턴스를 만들 때 새 개체로 전달되는 매개변수 목록
- 클래스에서 모든 속성의 매개변수를 정의할 필요는 없다.
- 매개변수는 필수, 선택, 기본값 등을 가질 수 있고, rest 매개변수일 수도 있다.
- 매개변수 이름은 속성 이름과 다를 수 있다.
- 속성 할당: 매개변수의 값을 속성 값에 할당한다. 클래스 멤버(속성)에 액세스하려면 this. 키워드를 사용한다.
- 클래스에는 최대 하나의 constructor만 사용
- constructor가 없을 경우 자동으로 제공됨.
class Car {
// properties
_make: string;
_color: string;
_doors: number;
// Constructor
constructor(make: string, color: string, doors = 4) {
this._make = make;
this._color = color;
this._doors = doors;
}
// Accessors
// Methods
}
- 속성 이름 앞에 있는 밑줄(_)은 선언할 때 꼭 필요한 것이 아니라, 생성자(constructor)를 통해 액세스하는 매개변수와 속성 선언을 구분하기 위한 것.
접근자 정의
- 클래스 속성은 기본적으로 public이기 때문에 직접 액세스 가능
- 타입스크립트는 getter와 setter를 지원하여 속성에 대한 액세스를 가로 챈다. → 각 개체에서 멤버에 액세스하는 방법을 보다 세밀하게 제어 가능
// Accessors
get make() {
return this._make;
}
set make(make) {
this._make = make;
}
- 프로그램에 반환하기 전에 유효성을 검사하거나 제약 조건을 적용할 수 있다. 다른 데이터 조작을 수행할 수도 있다.
- color 매개변수의 get/set을 정의하되 _color 속성 값에 문자열 연결
get color() {
return 'The color of the car is ' + this._color;
}
set color(color) {
this._color = color;
}
- doors 매개변수의 get/set 정의. _doors 값을 반환하기 전에 짝수인지 확인. 짝수가 아니라면 오류
get doors() {
return this._doors;
}
set doors(doors) {
if ((doors % 2) === 0) {
this._doors = doors;
} else {
throw new Error('Doors must be an even number');
}
}
클래스 메서드 정의
- 클래스 안에서 타입스크립트 함수 정의
- 개체나 클래스 안의 다른 함수에서 호출할 수도 있다.
- 클래스 메서드는 클래스가 수행할 동작을 설명하며 클래스에 필요한 다른 작업을 수행할 수 있다.
- function 키워드가 없다.
// Methods
accelerate(speed: number): string {
return `${this.worker()} is accelerating to ${speed} MPH.`;
}
brake(): string {
return `${this.worker()} is braking with the standard braking system.`;
}
turn(direction: 'left' | 'right'): string {
return `${this.worker()} is turning ${direction}.`;
}
// 이 함수는 다른 메서드 안에서 사용하기 위한 함수
worker(): string {
return this._make;
}
연습 - 클래스 인스턴스화
- new 키워드를 사용해 Car 클래스를 인스턴스화하고 매개변수 전달
let myCar1 = new Car('Cool Car Company', 'blue', 2);
- myCar1 개체에서 color에 액세스했을 때와 _color에 액세스했을 때를 비교해 보자
console.log(myCar1.color);
console.log(myCar1._color);
- _color 멤버는 클래스에 정의된 속성을 나타내고,
- color는 생성자에 전달하는 매개 변수
- _color를 참조하는 경우 'blue'를 반환하는 속성의 원시 데이터에 액세스
- color를 참조하는 경우 'The color of the car is blue'를 반환하는 속성에 get 또는 set을 통해 액세스

- doors는 선택적 매개변수이므로 초기화에서 생략해서 사용할 수 있다.
- myCar3 개체를 만들고, doors 매개변수값을 홀수로 해보자. → constructor에서 유효성 검사를 수행하지 않기 때문에 오류 없음. → (doors의 set 블록이 유효성을 검사한다)
let myCar3 = new Car('Galaxy Motors', 'red', 3);
- myCars3의 doors 값을 홀수로 설정해 보자 → 오류
let myCar3 = new Car('Galaxy Motors', 'red', 3);
myCar3.doors = 5;

Car 개체가 초기화될 때 유효성 검사하려면?
⇒ constructor 에 유효성 검사 코드 추가
class Car {
// properties
_make: string;
_color: string;
_doors: number;
// Constructor
constructor(make: string, color: string, doors = 4) {
this._make = make;
this._color = color;
if ((doors % 2) === 0) {
this._doors = doors;
} else {
throw new Error('Doors must be an even number.');
}
}
// Accessors
......
}
- 이젠 초기화에서 걸러낼 수 있음
let myCar3 = new Car('Galaxy Motors', 'red', 3);

클래스의 메서드 테스트
let myCar1 = new Car('Cool Car Company', 'blue', 2);
console.log(myCar1.color);
console.log(myCar1._color);
console.log(myCar1.accelerate(35));
console.log(myCar1.brake());

액세스 한정자
- 모든 클래스 멤버는 기본적으로 public → 클래스 외부에서 클래스 멤버에 액세스 가능
- 앞의 Car 클래스에서 _color(클래스에서 정의한 속성)과 color(생성자에서 정의한 매개변수)에 접근할 수 있었다.
- 일반적으로는 get, set 접근자를 통해서만 원시 데이터에 접근할 수 있도록 하는 것이 좋다.
- 메서드 함수에 대한 액세스를 제어할 수도 있다.
- 앞의 Car 클래스에서 메서드 함수 안에 또다른 worker 함수가 포함되어 있는데, 클래스 외부에서 worker 함수를 호출한다면 예상치 못한 결과가 생길 수 있다.
액세스 한정자
public
- 액세스 한정자를 지정하지 않는 경우 기본값은 public
- public 키워드를 사용하여 명시적으로 멤버를 public으로 설정할 수도 있다.
private
- private 키워드를 사용. 클래스 외부에서 멤버에 액세스할 수 없습니다.
protected
- private 한정자와 매우 비슷하게 동작다.
- 파생 클래스 내에서 protected로 선언된 멤버에도 액세스할 수 있다 (뒤에서 설명)
기타
- readonly : 멤버를 선언하거나 constructor에서 초기화할 때만 설정 가능
타입스크립트는 가져온 위치에 상관없이 두 가지 형식을 비교할 때 모든 멤버의 형식이 호환되는 경우 ‘형식 자체가 호환된다’.
만일 private 멤버나 protected 멤버를 포함하는 형식을 비교할 경우 좀더 다르게 동작함
→ 둘 중 하나에 private/protected 멤버가 있다면 다른 하나의 동일한 선언에도 private/protected 멤버를 포함해야 한다.
연습 - 액세스 한정자
1. myCar1에서 액세스할 수 있는 멤버들

2. _color, _doors, _make 속성과 worker 함수를 private으로 지정해 보자
class Car {
// properties
private _make: string;
private _color: string;
private _doors: number;
// Constructor
......
private worker(): string {
return this._make;
}
}
3. 다시 한번 myCar1에서 액세스할 수 있는 멤버를 확인하면?

정적 속성 정의
- 지금까지 정의된 클래스의 속성과 메서드는 인스턴스 속성
→ 클래스 개체의 각 인스턴스에서 인스턴스화되고 호출됨. - 정적 속성 : 정적 속성 및 메서드는 클래스의 모든 인스턴스에서 공유됨.
- 속성을 정적으로 만들려면 속성이나 메서드 이름 앞에 static 키워드 붙임
1. Car 클래스가 인스턴스화되는 횟수를 저장하는 numberOfCars 라는 속성을 static으로 지정하기
class Car {
// properties
private static numberOfCars: number = 0;
private _make: string;
private _color: string;
private _doors: number;
// Constructor
constructor(make: string, color: string, doors = 4) {
this._make = make;
this._color = color;
if ((doors % 2) === 0) {
this._doors = doors;
} else {
throw new Error('Doors must be an even number.');
}
Car.numberOfCars++;
}
// ......
}
2. 정적 메서드를 지정할 수도 있다.
public static getNumberOfCars(): number {
return Car.numberOfCars;
}
3. 인스턴스를 만들고 인스턴스 수를 반환해 보자
let myCar1 = new Car('Cool Car Company', 'blue', 2);
let myCar2 = new Car('Galaxy Motors', 'blue', 2);
console.log(Car.getNumberOfCars()); // 2
let myCar3 = new Car('Galaxy Motors', 'white');
console.log(Car.getNumberOfCars()); // 3
상속을 사용해 클래스 확장
- (예) Car 클래스를 확장해 eletricCar 클래스를 만들 수 있다
- ElectricCar 클래스는 Car 클래스의 속성 및 메서드를 상속하지만 고유 특성 및 동작을 가질 수 있다.
- Car 클래스를 확장해서 새 클래스를 만든다음 빌드할 수 있다.

- extends 키워드 사용
- electricCar 클래스는 Car 클래스의 하위 클래스 (기본 클래스는 슈퍼 클래스, 부모 클래스라고 부름)
상속을 사용하는 이유
- 코드 재사용 가능성. 한 번 개발하여 여러 곳에서 다시 사용할 수 있습니다. 이를 통해 코드의 중복성을 방지할 수도 있습니다.
- 하나의 기본 클래스를 사용하여 계층 구조에서 원하는 수의 하위 클래스를 파생할 수 있습니다. 예를 들어 Car 계층 구조의 하위 클래스에는 SUV 클래스나 Convertible 클래스도 포함될 수 있습니다.
- 유사한 기능이 있는 여러 다른 클래스에서 코드를 변경하는 대신 기본 클래스에서 한 번만 변경하면 됩니다.
메서드 재정의
- 기본 클래스의 함수와 동일한 이름으로 하위 클래스에 함수를 만들지만 기능이 서로 다를 때 발생
연습 - 클래스 확장
1. Car 클래스를 확장한 ElectricCar 클래스 만들기
class Car { ... }
class ElectricCar extends Car {
// properties unique to Electric Car
// Constructor
// Accessors
// Methods
}
2. ElectricCar의 고유 속성 선언
class ElectricCar extends Car {
// properties unique to Electric Car
private _range: number;
// Constructor
// Accessors
// Methods
}
3. 하위 클래스의 constructor는 기본 클래스의 constructor와 몇 가지면에서 다르다.
- 매개 변수 목록에는 기본 클래스와 하위 클래스의 모든 속성이 포함될 수 있습니다. (TypeScript의 모든 매개 변수 목록과 마찬가지로 필수 매개 변수가 선택적 매개 변수 앞에 와야 합니다.)
- constructor의 본문에서 super() 키워드를 추가하여 기본 클래스의 매개 변수를 포함해야 합니다. super 키워드는 실행될 때 기본 클래스의 constructor를 실행합니다.
- 하위 클래스의 속성을 참조할 때 super 키워드가 this.에 대한 참조 앞에 와야 합니다.
// Constructor
constructor(make: string, color: string, range: number, doors = 2) {
super(make, color, doors);
this._range = range;
}
4. range 매개변수의 get과 set 접근자 정의
// Accessors
get range() {
return this._range;
}
set range(range) {
this._range = range;
}
5. charge 메서드 정의
// Methods
charge() {
console.log(this.worker() + " is charging.");
}
→ worker는 private으로 정의했기 때문에 Car 클래스 안에서만 액세스 가능
Property 'worker' is private and only accessible within class 'Car'.
6. 위 문제를 해결하려면 private 대신 protected로 변경.
→ 이렇게 하면 Car 클래스의 하위 클래스가 함수를 사용할 수 있다.
→ 클래스에서 인스턴스화한 개체에 사용할 수 있는 멤버에는 나타나지 않는다.
class Car {
...
// 이 함수는 다른 메서드 안에서 사용하기 위한 함수
protected worker(): string {
return this._make;
}
7. electricCar 클래스 테스트하기
let spark = new ElectricCar('Spark Motors', 'silver', 124, 2);
let eCar = new ElectricCar('Electric Car Co.', 'black', 263);
console.log(eCar.doors);
spark.charge();

8. 메서드 재정의
- ElectricCar 클래스에서 brake 메서드 정의. 이 때 brake 메서드의 매개변수 시그니처와 반환 형식은 Car 클래스의 brake 메서드와 같아야 한다.
// Methods
charge() {
console.log(this.worker() + " is charging.");
}
brake(): string {
return `${this.worker()} is braking with the regenerative braking system.`;
}
9. 메서드 테스트
console.log(spark.brake());

연습 - 클래스 모양을 확인하는 인터페이스 선언
- 인터페이스를 사용하여 개체의 필수 속성과 해당 형식을 설명하는 ‘코드 계약’을 설정한다.
→ 인터페이스를 사용하면 클래스 인스턴스 모양을 확인할 수 있다.
1. Car 클래스의 속성과 메서드를 설명하는 Vehicle 인터페이스 선언
- 클래스의 속성이 아니라 생성자의 매개변수가 포함된다.
- 인터페이스는 클래스의 퍼블릭 쪽만 설명할 수 있고 프라이빗 멤버는 포함할 수 없다.
interface Vehicle{
make: string;
color: string;
doors: number;
accelerate(speed: number): string;
brake(): string;
turn(direction: 'left' | 'right'): string;
}
2. Car 클래스에서 Vehicle 인터페이스 구현
- implements 키워드 사용
- 클래스의 세부 정보를 빌드할 때 Vehicle 인터페이스에 설명된 코드 계약을 따르는지 확인함.
interface Vehicle{
make: string;
color: string;
doors: number;
accelerate(speed: number): string;
brake(): string;
turn(direction: 'left' | 'right'): string;
}
class Car implements Vehicle {
......
}
디자인 고려 사항
- 클래스와 인터페이스를 각각 언제 사용하면 좋을까?
인터페이스를 사용해야 하는 경우
- TypeScript가 JavaScript로 변환 컴파일되면 인터페이스가 제거된다. → 결과 파일에서 공간을 차지하지 않으며 실행될 코드에 부정적인 영향을 주지 않는다.
- 클래스가 있어야만 인터페이스를 사용할 수 있는 다른 프로그래밍 언어와 달리 TypeScript에서는 클래스 없이 인터페이스를 사용하여 데이터 구조를 정의할 수 있다.
- 인터페이스를 사용하여 함수의 매개 변수 개체를 정의하고 다양한 프레임워크 속성의 구조를 정의하고, 원격 서비스 또는 API에서 개체가 어떻게 보이는지 정의할 수 있다.
- 풀스택 애플리케이션을 만들고 있다면, 데이터를 구조화하는 방법을 정의해야 한다.
(예) 개에 대한 정보를 저장해야 한다면
interface Dog {
id?: number;
name: string;
age: number;
description: string;
}
- 인터페이스는 클라이언트와 서버 코드 모두 공유 모듈에서 사용할 수 있다 → 데이터 구조를 동일하게 유지할 수 있다. (예) 클라이언트에서 정의하는 서버 API에서 개를 검색하는 코드를 포함할 수 있다.
async loadDog(id: number): Dog {
return await (await fetch('demoURL')).json() as Dog;
}
클래스를 사용해야 하는 경우
- 인터페이스와 클래스의 주요 차이점은 클래스를 사용하여 구현 세부 정보를 정의할 수 있다는 것
- 인터페이스만이 데이터의 구조화 방법을 정의한다.
- 클래스를 사용하여 메서드, 필드, 속성을 정의할 수 있다. 클래스는 템플릿 개체를 위한 방법도 제공하는데, 기본값을 정의한다.
- (예) 서버에서 개를 데이터베이스에 로드하거나 저장하는 코드 데이터베이스에서 데이터를 관리하는 일반적인 방법은 ‘활성 레코드 패턴’
→ 데이터 자체에 save, load 등의 메서드가 있음. - Dog 인터페이스를 사용해 동일한 속성 및 구조가 있는지 확인하고, 작업을 하는데 필요한 코드를 추가할 수 있다.
interface Dog {
id?: number;
name: string;
age: number;
description: string;
}
class DogRecord implements Dog {
id: number;
name: string;
age: number;
description: string;
constructor( {name, age, description, id = 0}: Dog) {
this.id = id;
this.name = name;
this.age = age;
this.description = description;
}
static load(id: number): DogRecord {
// code to load dog from database
return dog;
}
save() {
// code to save dog to database
}
}
인터페이스에 관해 기억해야 할 TypeScript의 주요 기능 중 하나는 클래스가 필요하지 않다는 것입니다. 이 때문에 전체 클래스 구현을 만들지 않고도 데이터 구조를 정의하는 기능이 필요할 때마다 사용할 수 있습니다.
(이 글의 원문 보기 : Declare and instantiate classes in Typescript)
Declare and instantiate classes in TypeScript - Training
Learn how to declare and instantiate classes in TypeScript.
learn.microsoft.com
'javascript' 카테고리의 다른 글
[Typescript] 형식화된 함수 개발 (0) | 2024.02.07 |
---|---|
[Typescript] 인터페이스 구현하기 (0) | 2024.02.07 |
캔버스 기본 도형 그리기 - 사각형 외 (0) | 2023.02.15 |
[캔버스] 기본 도형 그리기 (0) | 2023.02.15 |
2023년부터는 나이가 줄어든다? - 만 나이 계산하기 (0) | 2023.02.09 |