클래스와 상속this를 반환 타입으로 사용하기인터페이스인터페이스와 타입 별칭(type alias)의 차이1. 타입 별칭은 더 일반적이어서 타입 별칭의 오른편에는 타입 표현식(타입, &, | 등의 타입 연산자)을 포함한 모든 타입이 등장 가능함2. 인터페이스를 상속할 때, 타입스크립트는 상속받는 인터페이스의 타입에 상위 인터페이스를 할당할 수 있는지 확인함3. 이름과 범위가 같은 인터페이스가 여러 개 있다면 이들이 자동으로 합쳐짐(type alias가 여러개면 컴파일 타임 에러 발생) — 선언합침(declaration merging)구현인터페이스 구현 vs 추상 클래스 상속Index Signature (인터페이스로 동적으로 필드가 추가되는 객체를 표현할때)클래스는 구조 기반 타입을 지원함클래스는 값과 타입을 모두 선언한다다형성(Generics)믹스인 — 아주 좋게 쓸수 있을것 같은데 아직 제대로 이해못함데코레이터final 클래스 흉내내기
클래스와 상속
type Color = 'Black' | 'White' type File = 'A' | 'B' | 'C' | 'D' | 'E' | 'F' | 'G' | 'H' type Rank = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 class Position { constructor( private file: File, // this.file 이 생기고, 해당 argument를 거기에 할당하는 로직까지 포함 private rank: Rank ) { } } class Piece { protected position: Position constructor( private readonly color: Color, file: File, rank: Rank ) { this.position = new Position(file, rank); } }
abstract class Piece { constructor( // ... moveTo(position: Position) { this.position = position; } abstract canMoveTo(position: Position): boolean }
this를 반환 타입으로 사용하기
let set = new Set(); set.add(1).add(2).add(3); set.has(2); set.has(4) class Set { add(value: number): Set { // ... } } class MutableSet extends Set { add(value: number): MutableSet { // ... } } // 이렇게 되면 Set을 상속받는 클래스에서 다 오버라이드를 해주어야 함
class Set { add(value: number): this { // ... } }
인터페이스
- 타입 별칭(type alias)처럼 인터페이스도 타입에 이름을 지어주는 수단이므로 인터페이스를 사용하면 타입을 더 깔끔하게 정의할 수 있다
- type alias와 interface는 문법만 다를 뿐 거의 같은 기능을 수행함
인터페이스와 타입 별칭(type alias)의 차이
1. 타입 별칭은 더 일반적이어서 타입 별칭의 오른편에는 타입 표현식(타입, &, | 등의 타입 연산자)을 포함한 모든 타입이 등장 가능함
type A = number; type B = A | string; // 위와 같은 타입 별칭 코드는 인터페이스로 다시 작성할 수 없음
2. 인터페이스를 상속할 때, 타입스크립트는 상속받는 인터페이스의 타입에 상위 인터페이스를 할당할 수 있는지 확인함
interface A { good(x: number): string bad(x: number): string } interface B extends A { good(x: string | number): string bad(x: string): string // 에러 TS2430: 인터페이스 B 는 인터페이스 'A'를 올바르게 상속받지 않음 // 'number' 타입은 'string' 타입에 할당할 수 없음 }

- 위의 타입을
인터섹션
(&)로 바꾸면 타입스크립트는 확장하는 타입을 최대한 조합하는 방향으로 동작함
type A1 = { good(x: number): string bad(x: number): string } type B1 = A1 & { good(x: string | number): string bad(x: string) : string // bad(x : number | string): string 으로 됨 }
3. 이름과 범위가 같은 인터페이스가 여러 개 있다면 이들이 자동으로 합쳐짐(type alias가 여러개면 컴파일 타임 에러 발생) — 선언합침(declaration merging)
interface User { name: string } interface User { age: number } // 이제 User는 name, age 두 개의 필드를 가짐 type User = { // 에러 TS2300: 중복된 식별자 'User' name: string } type User = { // 에러 TS2300: 중복된 식별자 'User' age: number }
- 그러나 인터페이스끼리 충돌해서는 안됨. 같은 프로퍼티가 다른 타입을 가지는 식의 충돌 x
interface User { age: string } interface User { age: number } // 다른 프로퍼티 선언도 같은 타입을 가져야 함
구현
- 클래스를 선언할 때
implements
라는 키워드를 이용해 특정 인터페이스를 만족시킴을 표현할 수 있음
interface Animal { eat(food: string): void sleep(hours: number): void } class Cat implements Animal { eat(food: string) { console.log('Ate some', food, '. Mmm!'); } sleep(hours: number) { console.info('Slept for', hours, 'hours'); } }
- 인터페이스로 인스턴스 프로퍼티를 정의할 수 있지만,
private
,public
,protected
같은 가시성 한정자는 사용할 수 없고static
키워드도 사용할 수 없음.readonly
는 가능
- 여러 인터페이스 구현 가능
인터페이스 구현 vs 추상 클래스 상속
- 인터페이스 구현은 추상 클래스 상속과 아주 비슷함
- 하지만 인터페이스가 더 범용적으로 쓰이며 가벼운 반면, 추상 클래스는 특별한 목적과 풍부한 기능을 갖는다는 점이 다름
인터페이스 | 추상클래스 |
형태를 정의하는 수단 | 오직 클래스만 정의 |
값 수준에서 이는 객체, 배열, 함수, 클래스, 클래스 인스턴스를 정의할 수 있음 | 클래스만 정의 |
아무런 자바스크립트 코드를 만들지 않으며 컴파일 타임에만 존재함 | 런타임의 자바스크립트 클래스 코드를 만듬 |
제공 x | 추상 클래스는 생성자와 기본 구현을 가질 수 있으며, 프로퍼티와 메서드에 접근 한정자를 지정할 수 있음 |
Index Signature (인터페이스로 동적으로 필드가 추가되는 객체를 표현할때)
interface StringArray { [index: number]: string; } const myArray: StringArray = getStringArray(); const secondItem = myArray[1];
클래스는 구조 기반 타입을 지원함
- 타입스크립트는 클래스를 비교할 때 다른 타입과 달리 이름이 아니라 구조를 기준으로 삼는다
- 클래스는 자신과 똑같은 프로퍼티와 메서드를 정의하는 기존의 일반 객체를 포함해, 클래스의 형태를 공유하는 다른 모든 타입과 호환됨
- C#, 자바, 스칼라를 포함해 이름으로 클래스 타입을 지정하는 언어와 차이가 있음!
class Zebra { trot() { // ... } } class Poodle { trot() { // ... } } // Zebra 타입의 파라미터이지만, 같은 구조를 가진 Poodle을 건네도 에러 발생안함 function ambleAround(animal: Zebra) { animal.trot(); }
- 그러나 클래스가
private
이나protected
필드를 갖는다면 할당하려는 클래스나 서브클래스의 인스턴스가 아니라면 할당할 수 없다고 판정함
class A { private x = 1; } class B extends A { } function f(a: A) { } f(new A()); f(new B()); f({ x: 1 }); // 에러 A의 'x' 프로퍼티는 private이지만, { x: number} 는 private 이 아님
클래스는 값과 타입을 모두 선언한다
- 값과 타입은 타입스크립트에서 별도의 네임스페이스에 존재함 → 컴패니언 타입 같은 기능도 구현 가능함
- 한편, 클래스와 열거형은 특별함. 이들은 타입 네임스페이스에 타입을, 값 네임스페이스에 값을 동시에 생성한다는 점에서 특별
class C { } let c: C = new C; // 앞의 C는 C 클래스의 인스턴스 타입. 뒤의 C는 값 C enum E { F, G }; let e: E = E.F; // 앞의 E는 E 열거형의 타입, 뒤의 E는 값 E
- 위 예제에서 타입 C는 C 클래스의 인스턴스를 가리킴. 그렇다면 C 클래스 자체는?
typeof
키워드를 사용해서 가리킬 수 있음
type State = { [key: string]: string } class StringDatabase { state: State = {} get(key: string): string | null { return key in this.state ? this.state[key] : null; } set(key: string, value: string): void { this.state[key] = value; } static from(state: State) { let db = new StringDatabase(); for (let key in state) { db.set(key, state[key]); } return db; } }
// StringDatabase 인스턴스 타입 interface StringDatabase { state: State get(key: string): string | null set(key: string, value: string): void } // typeof StringDatabase 의 생성자 타입 interface StringDatabaseConstructor { new(): StringDatabase from(state: State): StringDatabase }
- 위의 두 인터페이스를 합치면 StringDatabase 클래스의 생성자와 인스턴스가 완성됨
new()
코드를 생성자 시그니처(constructor signature)라 부르며, 생성자 시그니처는new
연산자로 해당 타입을 인스턴스화 할 수 있음을 정의하는 타입스크립트의 방식- 타입스크립트는 구조를 기반으로 타입을 구분하기 때문에 이 방식(’클래스란 new로 인스턴스화 할 수 있는 어떤 것’)이 클래스가 무엇인지를 기술하는 최선임
클래스 정의는 용어를 값 수준과 타입 수준으로 생성할 뿐 아니라, 타입 수준에서는 두 개의 용어를 생성.
하나는 클래스의 인스턴스를 가리키며, 다른 하나는(typeof 타입 연산자로 얻을 수 있는) 클래스 생성자 자체를 가리킴
다형성(Generics)
- 함수와 타입처럼, 클래스와 인터페이스도 기본값과 상한/하한 설정을 포함한 다양한 제네릭 타입 매개변수 기능을 지원함
- 제네릭 타입의 범위는 클래스나 인터페이스 전체가 되게 할 수도 있고 특정 메서드로 한정할 수도 있음
class MyMap<K, V> { constructor(initialKey: K, initialValue: V) { // ... } get(key: K): V { // ... } merge<K1, V1>(map: MyMap<K1, V1>): MyMap<K | K1, V | V1> { // ... } static of<K, V>(k: K, v: V): MyMap<K, V> { // ... } } interface MyMap<K, V> { get(key: K): V set(key: K, value: V): void }
- class와 함께 제네릭을 선언했으므로 클래스 전체에서 타입 사용 가능함. 모든 인스턴스 메서드와 인스턴스 프로퍼티에서 K, V 사용가능
- constructor에는 제네릭 타입 선언할 수 없음. constructor 대신 class 선언에 사용해야 함
- 인스턴스 메서드는 클래스 수준 제네릭 사용가능하고 자신만의 제네릭도 추가 선언 가능함
- 정적 메서드는 클래스의 인스턴스 변수에 값 수준에서 접근할 수 없듯이 클래스 수준의 제네릭 사용 불가. 따로 정의해야 함
믹스인 — 아주 좋게 쓸수 있을것 같은데 아직 제대로 이해못함
- 필요한 수의 믹스인을 클래스에 제공함으로 더 풍부한 동작을 제공할 수 있으며 타입 안전성도 보장됨
- 믹스인은 동작을 캡슐화할 뿐 아니라 동작을 재사용할 수 있도록 도와줌
데코레이터
TSC 플래그 : experimentalDecorators
데코레이터는 아직 실험 단계의 기능임. 즉, 미래에도 호환된다는 보장이 없고 아예 타입스크립트에서 삭제될 수도 있으므로 TSC 플래그를 따로 설정해야 사용할 수 있음
”experimentalDecorators”: true를 설정한 다음 사용하면 된다
- 타입스크립트의 실험적 기능으로 클래스, 클래스 메서드, 프로퍼티, 메서드 매개변수를 활용한 메타 프로그래밍에 깔끔한 문법을 제공함
final 클래스 흉내내기
- final 키워드는 클래스나 메서드를 확장하거나 오버라이드 할 수 없게 만드는 기능임
// 방법 1 : private 생성자. 상속만 불가한 것이 아니라 아예 new로 인스턴스 생성이 막혀버림 class MessageQueue { private constructor(private messages: string[]) { } } // 방법 2: 위의 방법에다가 정적 팩토리를 추가함. 상속 불가 & 인스턴스 생성 가능 class MessageQueue { private constructor(private messages: string[]) { } static create(messages: string[]) { return new MessageQueue(messages); } }