2024년 2월 26일 작성
생성자 Parameter에
Interface 구현 (
TypeScript Class Type - 객체 설계도
TypeScript의 class는 객체의 설계도로, 객체의 속성과 method를 정의하고, 객체를 생성하기 위한 template 역할을 합니다.
Class
- TypeScript가 지원하는 class는 JavaScript ES6의 class와 유사하지만, 몇 가지 TypeScript만의 고유한 확장 기능이 있습니다.
- TypeScript의 class는 정적 typing과 몇 가지 추가 기능을 제공하여 class를 더욱 강력하고 안전하게 만듭니다.
Class Definition
- JavaScript와 TypeScript의 class 정의 방식은 정적 typing 말고도, class property(member 변수) 선언 여부에서도 차이가 있습니다.
TypeScript에서 Class 정의하기
- JavaScript ES6의 class는 class body에 method만을 포함할 수 있습니다.
- class body에 class property를 선언할 수 없고, 반드시 생성자 내부에서 class property를 선언하고 초기화합니다.
// person.js
class Person {
constructor(name) {
this.name = name; // class property의 선언과 초기화
}
walk() {
console.log(`${this.name} is walking.`);
}
}
- JavaScript ES6에서는 문제없이 실행되는 code이지만, file의 확장자를
ts로 바꾸어 TypeScript file로 변경한 후 compile하면 compile error가 발생합니다.
person.ts(4,10): error TS2339: Property 'name' does not exist on type 'Person'.
person.ts(8,25): error TS2339: Property 'name' does not exist on type 'Person'.
TypeScript에서 Class 정의하기
- TypeScript class는 class body에 class property를 사전에 선언해야 합니다.
// person.ts
class Person {
name: string; // class property 사전 선언
constructor(name: string) {
this.name = name; // class property에 값 할당
}
walk() {
console.log(`${this.name} is walking.`);
}
}
const person = new Person('Lee');
person.walk(); // Lee is walking
접근 제한자 (Access Modifier)
- TypeScript class는 class 기반 객체 지향 언어가 지원하는 접근 제한자
public,private,protected를 지원하며, 의미 또한 기본적으로 동일합니다.- 접근 제한자는 class 내부의 property과 method의 접근성을 제어하는 keyword입니다.
- 접근 제한자는 class를 사용하는 외부 code에서 class 내부의 특정 member에 접근할 수 있는지 여부를 결정합니다.
| 접근 가능성 | public | protected | private |
|---|---|---|---|
| Class 내부 | O | O | O |
| 자식 Class 내부 | O | O | x |
| Class Instance | O | x | x |
public으로 지정하고자 하는 member 변수와 method는 접근 제한자를 생략하면 됩니다.- TypeScript의 경우, 접근 제한자를 생략한 class property와 method는 암묵적으로
public이 선언됩니다. - 다른 class 기반 언어의 경우, 접근 제한자를 명시하지 않으면 암묵적으로
protected로 지정되어 package level로 공개됩니다.
- TypeScript의 경우, 접근 제한자를 생략한 class property와 method는 암묵적으로
class Foo {
public x: string;
protected y: string;
private z: string;
constructor(x: string, y: string, z: string) {
// public, protected, private 접근 제한자 모두 class 내부에서 참조 가능함
this.x = x;
this.y = y;
this.z = z;
}
}
const foo = new Foo('x', 'y', 'z');
// public 접근 제한자는 class instance를 통해 class 외부에서 참조 가능함
console.log(foo.x);
// protected 접근 제한자는 class instance를 통해 class 외부에서 참조할 수 없음
console.log(foo.y); // error TS2445: Property 'y' is protected and only accessible within class 'Foo' and its subclasses.
// private 접근 제한자는 class instance를 통해 class 외부에서 참조할 수 없음
console.log(foo.z); // error TS2341: Property 'z' is private and only accessible within class 'Foo'.
class Bar extends Foo {
constructor(x: string, y: string, z: string) {
super(x, y, z);
// public 접근 제한자는 자식 class 내부에서 참조 가능함
console.log(this.x);
// protected 접근 제한자는 자식 class 내부에서 참조 가능함
console.log(this.y);
// private 접근 제한자는 자식 class 내부에서 참조할 수 없음
console.log(this.z); // error TS2341: Property 'z' is private and only accessible within class 'Foo'.
}
}
생성자 Parameter에 접근 제한자 선언
- 생성자(constructor) parameter에도 접근 제한자를 선언할 수 있습니다.
- 접근 제한자가 사용된 생성자 parameter는 암묵적으로 class property로 선언되고, 생성자 내부에서 별도의 초기화가 없어도 암묵적으로 초기화가 수행됩니다.
생성자 Parameter에 private, public 접근 제한자 선언
private접근 제한자가 사용되면, class 내부에서만 참조 가능하고,public접근 제한자가 사용되면 class 외부에서도 참조가 가능합니다.
class Foo {
// 접근 제한자가 선언된 생성자 parameter 'x'는 class property로 선언되고 지동으로 초기화됨
constructor(public x: string) { }
}
const foo = new Foo('Hello');
console.log(foo); // Foo { x: 'Hello' }
// public이 선언된 'foo.x'는 class 외부에서도 참조가 가능함
console.log(foo.x); // Hello
class Bar {
// 접근 제한자가 선언된 생성자 parameter 'x'는 class property로 선언되고 지동으로 초기화됨
constructor(private x: string) { }
}
const bar = new Bar('Hello');
console.log(bar); // Bar { x: 'Hello' }
// private이 선언된 'bar.x'는 class 내부에서만 참조 가능함
console.log(bar.x); // Property 'x' is private and only accessible within class 'Bar'.
생성자 Parameter에 접근 제한자를 선언하지 않은 경우
- 생성자 parameter에 접근 제한자를 선언하지 않으면, 생성자 parameter는 생성자 내부에서만 유효한 지역 변수가 되어, 생성자 외부에서 참조가 불가능합니다.
class Foo {
// 'x'는 생성자 내부에서만 유효한 지역 변수임 (접근 제한자가 선언되지 않아 class property 선언과 초기화가 되지 않음)
constructor(x: string) {
console.log(x);
}
}
const foo = new Foo('Hello');
console.log(foo); // Foo {}
읽기 전용 속성 (Readonly Property)
- TypeScript class의
readonlykeyword는 변수 할당 시의constkeyword와 유사합니다. readonly가 선언된 class property는 선언 시 또는 생성자 내부에서만 값을 할당할 수 있습니다.- 이 외의 경우에는 값을 할당할 수 없고, 오직 읽기만 가능한 상태가 됩니다.
- 일반적으로 상수를 선언할 때 사용합니다.
class Foo {
private readonly MAX_LEN: number = 5;
private readonly MSG: string;
constructor() {
this.MSG = 'hello';
}
log() {
// readonly가 선언된 property는 재할당이 금지됨
this.MAX_LEN = 10; // Cannot assign to 'MAX_LEN' because it is a constant or a read-only property.
this.MSG = 'Hi'; // Cannot assign to 'MSG' because it is a constant or a read-only property.
console.log(`MAX_LEN : ${this.MAX_LEN}`); // MAX_LEN : 5
console.log(`MSG : ${this.MSG}`); // MSG : hello
}
}
new Foo().log();
Static Member
- TypeScript에서
statickeyword를 사용하여 class member를 정적으로 선언할 수 있습니다.- class member에는 method(함수)와 property(속성)가 있으며, 따라서 static member도 static method와 static property로 나뉩니다.
Static Method
- JavaScript ES6의 class에서
statickeyword는 class의 정적(static) method를 정의합니다.- TypeScript에서도 JavaScript ES6와 동일한 방식으로 사용할 수 있습니다.
- 정적 method는 class의 instance가 아닌 class 이름으로 호출하기 때문에, class의 instance를 생성하지 않아도 호출할 수 있습니다.
- 정적 method는
this를 사용할 수 없으며, 정적 method 내부의this는 class의 instance가 아닌 class 자신을 가리킵니다.
- 정적 method는
class Foo {
constructor(prop) {
this.prop = prop;
}
static staticMethod() {
return 'staticMethod';
}
prototypeMethod() {
return this.prop;
}
}
// 정적 method는 class 이름으로 호출함
console.log(Foo.staticMethod()); // staticMethod
// 정적 method는 instance로 호출할 수 없음
const foo = new Foo(123);
console.log(foo.staticMethod()); // Uncaught TypeError: foo.staticMethod is not a function.
Static Property
- TypeScript에서는 static keyword를 class property에도 사용할 수 있습니다.
- 정적 method와 마찬가지로, 정적 class property는 instance가 아닌 class 이름으로 호출하며, class의 instance를 생성하지 않아도 호출할 수 있습니다.
class Foo {
// 생성된 instance의 갯수
static instanceCounter = 0;
constructor() {
// 생성자가 호출될 때마다 counter를 1씩 증가시킴
Foo.instanceCounter++;
}
}
var foo1 = new Foo();
var foo2 = new Foo();
// 정적 class property는 class 이름으로 호출함
console.log(Foo.instanceCounter); // 2
// 정적 class property는 instance로 호출할 수 없음
console.log(foo2.instanceCounter); // error TS2339: Property 'instanceCounter' does not exist on type 'Foo'.
추상 Class (Abstract Class)
- 추상 class는 하나 이상의 추상 method를 포함하며, 일반 method도 포함할 수 있습니다.
- 추상 method는 내용이 없이 method 이름과 type만이 선언된 method입니다.
-
추상 class와 추상 method를 선언할 때는
abstractkeyword를 사용합니다. - 추상 class는 직접 instance를 생성할 수 없고 상속만을 위해 사용됩니다.
- 추상 class를 상속한 class는 추상 class의 추상 method를 반드시 구현해야 합니다.
interfacetype은 추상 class와 비슷하지만, 일반 method를 선언할 수 없다는 점에서 추상 class와 다릅니다.interfacetype은 모든 method가 추상 method입니다.- 추상 class는 하나 이상의 추상 method와 일반 method를 포함할 수 있습니다.
abstract class Animal {
// 추상 method
abstract makeSound(): void;
// 일반 method
move(): void {
console.log('roaming the earth...');
}
}
// 직접 instance를 생성할 수 없음
new Animal(); // error TS2511: Cannot create an instance of the abstract class 'Animal'.
class Dog extends Animal {
// 추상 class를 상속한 class는 추상 class의 추상 method를 반드시 구현해야 함
makeSound() {
console.log('bowwow~~');
}
}
const myDog = new Dog();
myDog.makeSound();
myDog.move();
Interface 구현 (implements)
- class는
implementskeyword를 사용하여 특정 interface를 구현하겠다고 선언할 수 있습니다.- class가 interface의 계약을 준수하도록 강제합니다.
- class가 interface를 구현하는 경우, class는 interface에 정의된 모든 property와 method를 구현해야 합니다.
interface Vehicle {
model: string;
year: number;
displayDetails(): void;
}
class Car implements Vehicle {
model: string;
year: number;
constructor(model: string, year: number) {
this.model = model;
this.year = year;
}
displayDetails() {
console.log(`Model: ${this.model}, Year: ${this.year}`);
}
}
const myCar = new Car("Hyundai Sonata", 2020);
myCar.displayDetails();
- 하나 이상의 interface를 구현할 수도 있습니다.
interface Chargeable {
batteryLevel: number;
charge(): void;
}
interface Connectable {
isConnected: boolean;
connect(): void;
disconnect(): void;
}
class Smartphone implements Chargeable, Connectable {
batteryLevel: number;
isConnected: boolean;
constructor(batteryLevel: number) {
this.batteryLevel = batteryLevel;
this.isConnected = false;
}
charge() {
this.batteryLevel = 100;
console.log("Smartphone 충전 완료");
}
connect() {
this.isConnected = true;
console.log("Smartphone 연결");
}
disconnect() {
this.isConnected = false;
console.log("Smartphone 연결 해제");
}
}
const myPhone = new Smartphone(50);
myPhone.charge();
myPhone.connect();
myPhone.disconnect();