클래스는 값과 타입을 모두 선언한다
TypeScript의 거의 모든 것은 값 아니면 타입입니다.
// 값
let a = 1999
function b(){}
// 타입
type a = number
interface b {
(): void
}
값과 타입은 TypeScript에서 별도의 네임스페이스에 존재합니다.
용어를 어떻게 사용하는지를 보고 TypeScript가 알아서 이를 값 또는 타입으로 해석합니다.
// 값
let a = 1999
function b(){}
// 타입
type a = number
interface b {
(): void
}
if(a + 1 > 3){ // 문맥상 값 a로 추론
let x: a = 3 // 문맥상 타입 a로 추론
}
문맥을 파악해 해석하는 이 기능은 정말 유용합니다.
한편 클래스와 열거형은 타입 네임스페이스에 타입을, 값 네임스페이스에 값을 동시에 생성한다는 점에서 특별합니다.
class C {}
let c: C // ①
= new C // ②
enum E { F, G }
let e: E // ③
= E.F // ④
- 문맥상 C는 C 클래스의 인스턴스 타입을 가리킵니다.
- 문맥상 C는 C를 가리킵니다.
- 문맥상 E는 E 열거형의 타입을 가리킵니다.
- 문맥상 E는 값 E를 가리킵니다.
클래스를 다룰 때는 "이 변수는 이 클래스의 인스턴스여야 한다"라고 표현할 수 있는 방법이 필요한데, 이는 열거형도 마찬가지입니다.
클래스와 열거형은 타입 수준에서 타입을 생성하기 때문에 이런 'is-a'관계를 쉽게 표현할 수 있습니다.
런타임에 new로 클래스를 인스턴스화하거나, 클래스의 정적 메서드를 호출하거나, 메타 프로그래밍하거나, instanceof 연산을 수행하려면 클래스의 값이 필요합니다.
위의 예제에서 C는 C 클래스의 인스턴스를 가리켰습니다.
C 클래스 자체를 가리키려면 typeof 키워드를 사용하면 됩니다.
단순한 데이터베이스인 StringDatabases라는 클래스를 만들어보겠습니다.
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
}
StringDatabaseConstructor는 .from이라는 한 개의 메서드를 포함하며 new는 StringDatabase 인스턴스를 반환합니다.
두 인터페이스를 합치면 StringDatabase 클래스의 생성자와 인스턴스가 완성됩니다.
new() 코드를 생성자 시그니처(constructor signature)라 부르며, 생성자 시그니처는 new 연산자로 해당 타입을 인스턴스화할 수 있음을 정의하는 TypeScript의 방식입니다.
TypeScript는 구조를 기반으로 타입을 구분하기 때문에 이 방식('클래스란 new로 인스턴스화할 수 있는 어떤 것')이 클래스가 무엇인지를 기술하는 최선입니다.
앞의 예는 인수를 전혀 받지 않는 생성자이지만 인수를 받는 생성자도 선언할 수 있습니다.
예를 들어 아래는 StringDatabase가 선택적으로 초기 상태를 받도록 수정한 모습입니다.
class StringDatabase {
constructor(public state: State = {}){}
// ...
}
이 StringDatabase의 생성자 시그니처는 다음과 같습니다.
class StringDatabase {
constructor(public state: State = {}){}
// ...
}
interface StringDatabaseConstructor {
new(state?: State): StringDatabase
from(state: State): StringDatabase
}
클래스 정의는 용어를 값 수준과 타입 수준으로 생성할 뿐 아니라, 타입 수준에선 두 개의 용어를 생성했습니다.
하나는 클래스의 인스턴스를 가리키며, 다른 하나는 (typeof 타입 연산자로 얻을 수 있는) 클래스 생성자 자체를 가리킵니다.
'👶 TypeScript' 카테고리의 다른 글
믹스인(mixin) (0) | 2023.01.16 |
---|---|
다형성 (0) | 2023.01.16 |
클래스는 구조 기반 타입을 지원한다 (0) | 2023.01.16 |
인터페이스 구현 VS 추상 클래스 상속 (0) | 2023.01.15 |
인터페이스 구현 (0) | 2023.01.15 |