Item 8 타입 공간과 값 공간의 심벌 구분하기

Symbols

타입스크립트의 심벌(symbol)은 타입 공간이나 값 공간 중의 한 곳에 존재한다.

interface Cylinder {
  radius: number
  height: number
}
const Cylinder = (radius: number, height: number) => ({ radius, height })

function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape.radius
       // ~~~~~~~ Property 'radius' does not exist on type '{}'.
  }
}

이 둘을 구분할 수 있을까? 이름은 같지만 하나는 타입이고 하나는 값으로 쓰이며, 서로 관련이 없다. 이런 점이 가끔 문제를 야기하므로 문맥을 읽어야 한다.

리터럴 예시

// 심벌이 '타입'이다
type T1 = 'string literal'
type T2 = 123

// 심벌이 '값'이다
const v1 = 'string literal'
const v2 = 123

대입(=) 예시

TS 코드에서 타입과 값은 번갈아 나올 수 있다. 타입 선언(:) 또는 단언문(as) 다음에 나오는 심벌은 타입인 반면, = 다음에 나오는 모든 것은 값이다.

interface Person {
  first: string
  last: string
}

const p: Person = { first: 'Jane', last: 'Jacobs' }
// p, { first: 'Jane', last: 'Jacobs' }는 값이고, Person은 타입이다.

함수 예시

타입과 값이 반복적으로 번갈아 나올 수 있다.

function email(p: Person, subject: string, body: string): Response {
  // email, p, subject, body는 값
  // Person, string, Response는 타입
  // ...
}

클래스 예시

한편, 클래스는 값으로도 타입으로도 쓰일 수 있다.

class Cylinder {
  radius = 1
  height = 1
}

function calculateVolume(shape: unknown) {
  if (shape instanceof Cylinder) {
    shape        // OK, 타입은 Cylinder
    shape.radius // OK, 타입은 number
  }
}
  • 클래스가 타입으로 쓰일 때는 형태(속성과 메서드)가 사용된다. (cylinder: Cylinder, ...)

  • 클래스가 값으로 쓰일 때는 생성자가 사용된다. (new Cylinder(...))

따라서 클래스는 상황에 따라 다르게 동작한다.

const v = typeof Cylinder // 값이 'function'
type t = typeof Cylinder  // 타입이 typeof Cylinder

2번째 줄의 경우 t의 타입이 typeof Cylinder인데, 중요한 건 Cylinder가 인스턴스의 타입이 아니라는 것이다.

실제로 new 키워드를 사용할 때 볼 수 있는 생성자 함수이다.

declare let fn: T
const c = new fn() // 타입이 Cylinder

InstanceType 제너릭을 이용해 생성자 타입과 인스턴스 타입을 전환할 수 있다.

type c = InstanceType<typeof Cylinder> // 타입이 Cylinder

typeof 연산자 예시

타입으로 쓰일 때와 값에서 쓰일 때 다른 기능을 하는 것들 중에는 typeof 연산자가 있다.

type T1 = typeof p      // 타입은 Person
type T2 = typeof email  // 타입은 (p: Person, subject: string, body: string) => Response

const v1 = typeof p     // 값은 'object'
const v2 = typeof email // 값은 'function'
  • 타입의 관점에서 typeof값을 읽어 타입스크립트 타입을 반환한다. (TypeScript Docs)

    • 타입 공간의 typeof는 보다 큰 타입의 일부분으로 사용하거나 type 구문으로 이름을 붙일 수 있다.

  • 값의 관점에서 typeof자바스크립트 런타임의 typeof 연산자가 된다. (MDN Docs)

속성 접근자 [] 예시

속성 접근자인 []는 타입으로 쓰일 때에도 동일하게 동작한다. 하지만 obj['field']obj.field값이 동일하더라도 타입은 다를 수 있다.

따라서 타입의 속성을 얻을 때에는 반드시 첫 번째 방법(obj['field'])을 사용해야 한다.

const first: Person['first'] = p['first']  // 또는 p.first
// first, p['first']는 값
// Person['first']는 타입
  • Person['first']는 여기서 타입 맥락(: 뒤에)에 쓰였기 때문에 타입이다.

type PersonEl = Person['first' | 'last']  // 타입은 string
type Tuple = [string, number, Date]
type TupleEl = Tuple[number]              // 타입은 string | number | Date
  • 인덱스 위치에는 유니온 타입과 기본형 타입을 포함한 어떠한 타입이든 사용할 수 있다.

속성 접근자에 대한 내용은 Item 14에서 더 자세히 다룬다.

그 외 타입 공간과 값 공간 사이에서 다른 의미를 가지는 코드 패턴들

Code PatternJavaScriptTypeScript

this

JS의 this 키워드 (Item 49)

다형성(polymorphic) this라고 불리는 TS 타입이다. 서브클래스의 메서드 체인을 구현할 때 유용하다.

&, |

AND와 OR 비트연산

인터섹션과 유니온

const

const는 새 변수를 선언한다.

as const는 리터럴 또는 리터럴 표현식의 추론된 타입을 바꾼다.(Item 21)

extends

서브클래스(class A extends B)

  • 서브타입(interface A extends B)

  • 제너릭 타입의 한정자(Generic<T extends number>)를 정의한다.

in

루프 (for (key in object))

매핑된(mapped) 타입에 등장

Last updated