클래스는 자신과 똑같은 프로퍼티와 메서드를 정의하는 기존의 일반 객체를 포함해, 클래스의 형태를 공유하는 다른 모든 타입과 호환됨
C#, 자바, 스칼라를 포함해 이름으로 클래스 타입을 지정하는 언어와 차이가 있음!
그러나 클래스가 private이나 protected 필드를 갖는다면 할당하려는 클래스나 서브클래스의 인스턴스가 아니라면 할당할 수 없다고 판정함
클래스는 값과 타입을 모두 선언한다
값과 타입은 타입스크립트에서 별도의 네임스페이스에 존재함 → 컴패니언 타입 같은 기능도 구현 가능함
한편, 클래스와 열거형은 특별함. 이들은 타입 네임스페이스에 타입을, 값 네임스페이스에 값을 동시에 생성한다는 점에서 특별
위 예제에서 타입 C는 C 클래스의 인스턴스를 가리킴. 그렇다면 C 클래스 자체는? typeof 키워드를 사용해서 가리킬 수 있음
위의 두 인터페이스를 합치면 StringDatabase 클래스의 생성자와 인스턴스가 완성됨
new() 코드를 생성자 시그니처(constructor signature)라 부르며, 생성자 시그니처는 new 연산자로 해당 타입을 인스턴스화 할 수 있음을 정의하는 타입스크립트의 방식
타입스크립트는 구조를 기반으로 타입을 구분하기 때문에 이 방식(’클래스란 new로 인스턴스화 할 수 있는 어떤 것’)이 클래스가 무엇인지를 기술하는 최선임
💡
클래스 정의는 용어를 값 수준과 타입 수준으로 생성할 뿐 아니라, 타입 수준에서는 두 개의 용어를 생성.
하나는 클래스의 인스턴스를 가리키며, 다른 하나는(typeof 타입 연산자로 얻을 수 있는) 클래스 생성자 자체를 가리킴
다형성(Generics)
함수와 타입처럼, 클래스와 인터페이스도 기본값과 상한/하한 설정을 포함한 다양한 제네릭 타입 매개변수 기능을 지원함
제네릭 타입의 범위는 클래스나 인터페이스 전체가 되게 할 수도 있고 특정 메서드로 한정할 수도 있음
class와 함께 제네릭을 선언했으므로 클래스 전체에서 타입 사용 가능함. 모든 인스턴스 메서드와 인스턴스 프로퍼티에서 K, V 사용가능
constructor에는 제네릭 타입 선언할 수 없음. constructor 대신 class 선언에 사용해야 함
인스턴스 메서드는 클래스 수준 제네릭 사용가능하고 자신만의 제네릭도 추가 선언 가능함
정적 메서드는 클래스의 인스턴스 변수에 값 수준에서 접근할 수 없듯이 클래스 수준의 제네릭 사용 불가. 따로 정의해야 함
믹스인 — 아주 좋게 쓸수 있을것 같은데 아직 제대로 이해못함
필요한 수의 믹스인을 클래스에 제공함으로 더 풍부한 동작을 제공할 수 있으며 타입 안전성도 보장됨
믹스인은 동작을 캡슐화할 뿐 아니라 동작을 재사용할 수 있도록 도와줌
데코레이터
💡
TSC 플래그 : experimentalDecorators
데코레이터는 아직 실험 단계의 기능임. 즉, 미래에도 호환된다는 보장이 없고 아예 타입스크립트에서 삭제될 수도 있으므로 TSC 플래그를 따로 설정해야 사용할 수 있음
”experimentalDecorators”: true를 설정한 다음 사용하면 된다
타입스크립트의 실험적 기능으로 클래스, 클래스 메서드, 프로퍼티, 메서드 매개변수를 활용한 메타 프로그래밍에 깔끔한 문법을 제공함
final 클래스 흉내내기
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);
}
}
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을 상속받는 클래스에서 다 오버라이드를 해주어야 함
this를 반환타입으로 사용하지 않을 때
class Set {
add(value: number): this {
// ...
}
}
this를 반환타입으로 사용하면 MutableSet에서 add를 Override 할 필요가 없음
type A = number;
type B = A | string;
// 위와 같은 타입 별칭 코드는 인터페이스로 다시 작성할 수 없음
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 으로 됨
}
interface User {
name: string
}
interface User {
age: number
} // 이제 User는 name, age 두 개의 필드를 가짐
type User = { // 에러 TS2300: 중복된 식별자 'User'
name: string
}
type User = { // 에러 TS2300: 중복된 식별자 'User'
age: number
}
interface User {
age: string
}
interface User {
age: number
} // 다른 프로퍼티 선언도 같은 타입을 가져야 함
class Zebra {
trot() {
// ...
}
}
class Poodle {
trot() {
// ...
}
}
// Zebra 타입의 파라미터이지만, 같은 구조를 가진 Poodle을 건네도 에러 발생안함
function ambleAround(animal: Zebra) {
animal.trot();
}
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
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
}