TIR: Typescript | 클래스와 인터페이스 (6) 클래스 - 클래스

Lumpen·2023년 1월 23일
0

Typescript

목록 보기
8/17

클래스

클래스는 구조 기반 타입을 지원한다

타입스크립트는 클래스를 비교할 때 다른 타입과 달리 이름이 아니라 구조를 기준으로 삼는다

클래스는 자신과 똑같은 프로퍼티와 메소드를 저으이하는 기존 일반 객체를 포함헤 클래스의 형태를 공유하는 모든 타입과 호환된다
타입스크립트에서는 Zebra를 인수로 받는 함수에 Poodle을 전달한다고 반드시 에러를 발생시키지는 않는다

class Zebra {
	trot() {}
}

class Poodle {
	trot() {}
}

function ambleAround(animal: Zebra) {
	animal.trot()
}

let zebra = new Zebra
let poodle = new Poodle

ambleAround(zebra) // ok
ambleAround(poodle) // ok

계통 유전학적으로 둘은 전혀 다르지만
함수 관점에서는 두 크래스가 .trot() 을 구현하며
서로 호환되므로 아무 문제가 없다
타입스크립트는 구조를 기준으로 삼기 때문이다

단 private나 protexted 필드를 갖는 클래스는 다르다
클래스에 private나 protexted 필드가 있고, 할당하려는 클래스나 서브클래스의 인스턴스가 아니라면 할당할 수 없다고 판단한다

class A {
	private x = 1 
}

class B extends A {}
function f(a: A) {}

f(new A) // ok
f(new B) // ok

f({x: 1}) // error 
		// - {x: number} 타입은 매개변수 
        // 'A' 타입에 할당할 수 없음

클래스는 값과 타입 모두 선언한다

타입스크립트의 거의 모든 것은 값 아니면 타입이다

// 값
let a = 1999
function b() {}

// 타입
type a = number
interface b {
  (): void
}

값과 타입은 타입스크립트에서 별도의 네임스페이스에 존재한다
용어를 어떻게 사용하는지를 보고 타입스크립트가 알아서 값 또는 타입으로 추론한다

문맥을 파악하는 해당 기능은 유용하다
덕분에 컴패니언 타입 같은 멋진 기능을 구현할 수 있다
클래스와 열거형은 특별하다 이들은 타입 네임스페이스에 타입을, 값 네임스페이스에 값을 동시에 생성한다

class c {}
let c: C 	// 문맥상 C는 C 클래스의 인스턴스 타입을 가리킨다
	= new C  // 문맥상 C는 값 C 를 카리킨다

enum E {F, G}
let e: E // 문맥상 E는 E 열거형 타입을 카리킨다
	= E.F // 문막상 E는 값 E를 가리킨다

클래스를 다룰 때는 해당 변수가 어떤 클래스의 인스턴스인지 표현할 수 있는 방법이 필요한데 열거형도 마찬가지
클래스와 열거형은 타입 수준에서 타입을 생성하기 때문에 이런 is-a 관계를 쉽게 표현할 수 있다

런타임에 new 연산자로 클래스를 인스턴스화 하거나 클래스의 정적 메서드를 호출, 메타프로그래밍 하거나, instanceof 연산을 수행하려면 클래스의 값이 필요하다

위 예제에서 C는 클래스의 인스턴스를 카리킨다
C가 C 클래스 자체를 가리키게 하려면 typeof 키워드를 사용한다
(자바스크립트에서는 값 수준의 typeof 가 있고 타입스크립트에서는 타입 수준의 typeof 를 지원한다)

타입스크립트는 구조 기반으로 타입을 지정하기 때문에 특정 클래스가 정의한 것과 형태가 같은 모든 객체를 해당 클래스의 타입에 할당할 수 있다
(strict 모드 일 경우 - structural type system 을 적용 받아 이렇게 동작하지만 strict 모드를 해제 했을 경우에는 nominal type system 을 적용 받기 떄문에 구조가 아닌 객체의 이름을 기준으로 비교하게 된다)

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 ket in state) {
    	db.set(key, state[key])
    }
    
    return db
  }
}


// StringDatabase 의 interface
interface StringDatabase {
	state: State
  	get(key: string): string | null
  	set(key: string, value: string): void
}


// StringDatabase 의 생성자 interface
interface StringDatabaseConstructor {
	new(): StringDatabase
  	from(state: State): StringDatabase
}

StringDatabaseConstructor 는 from() 이라는 하나의 메서드를 포함하며
new() 는 StringDatabase 를 반환한다

두 인터페이스를 합치면 StringDatabase 클래스의 생성자와 인스턴스가 완성된다

new() 코드를 생성자 시그니처라고 하며, 생성자 시그니처는 new 연산자로
해당 타입을 인스턴스화할 수 있음을 정의하는 타입스크립트 방식이다

타입스크립트는 구조 기반으로 타입을 구분하기 때문에 클래스란 new로 인스턴스화 할 수 있는 어떤 것 이라는 해당 코드가 클래스를 기술하는 최선의 방법이다

생성자의 인수로 초기 상태를 받을 수 있도록 할 수도 있다

class StringDatabase {
	constructor(public state: State = {}) {} // state가 없으면 기본값 {}
  	// ....
}

interface StringDatabaseConstructor {
	new(state?: State): StringDatabase
  	from(state: State): StringDatabase
}

클래스 정의는 용어를 값 수준과 타입 수준으로 생성할 뿐만 아니라
타입 수준에서는 두 개의 용어를 생성한다
하나는 클래스의 인스턴스,
또 하나는 클래스 생성자 자체를 가리킨다

profile
떠돌이 생활을 하는. 실업자는 아니지만, 부랑 생활을 하는

0개의 댓글