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

타입스크립트의 심벌(symbol)은 타입 공간이나 값 공간 중 한 곳에 존재합니다. 이름이 같은 심벌이더라도 속하는 공간에 따라 서로 다른 것을 의미할 수 있기 때문에 혼란스러울 수 있습니다.

interface Cylinder {
  radius: number;
  height: number;
}

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

작성한 코드에 따라, 이후 상황에 따라서 Cylinder란 심벌은 값으로 쓰일 수도, 타입으로 쓰일 수도 있습니다. 이런 경우, 추후에 혼란을 일으킬 여지가 많습니다.

초기에 타입 공간과 값 공간, 각각에 대한 개념을 잡고자 한다면 TS Playground를 활용해보세요. TS가 JS로 컴파일링된 이후에도 심벌이 남아있다면 값일테고, 그렇지 않다면 타입일 겁니다.

타입과 값 구분하기

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

타입과 값 모두로 사용될 수 있는 경우

class

한편, class는 타입과 값 모두로 사용될 수 있습니다.

class Cylinder {
  radius = 1;
  height = 1;
}

// 여기서 Cylinder는 인터페이스로 사용되었습니다.
interface NamedCylinder extends Cylinder {
    name: string;
}

const namedCylinder: NamedCylinder = {
    radius: 1,
    height: 2,
    name: 'alan',
}

// 여기서 Cylinder는 생성자로 사용되었습니다.
const cylinder = new Cylinder();

클래스는 타입으로 쓰일 때 인터페이스로 사용되는 반면, 값으로 쓰일 때 생성자로 사용됩니다.

typeof

연산자 typeof도 타입과 값 모두에서 사용될 수 있는데, 이는 비슷하면서도 상당히 다릅니다.

  • 타입의 관점에서 typeof는 TS 상에서의 타입을 반환합니다.
  • 값의 관점에서 typeof는 JS 런타임의 연산자가 됩니다.

위쪽의 예시의 연장선을 통해 이를 살펴보면 아래와 같습니다.

const v = typeof Cylinder; // v는 'function' string value입니다.
type T = typeof Cylinder; // T는 Cylinder 생성자 함수의 Type입니다.

여기서 흥미로운 것은, 아래에 있는 타입 관점의 typeof Cylinder는 인스턴스의 타입이 아닌, 생성자 함수의 타입이 된다는 점입니다. 만약 이것을 인스턴스 타입으로 활용하고자 한다면 아래와 같이 전환해야 합니다.

type T = typeof InstanceType<typeof Cylindar>;

타입 프로퍼티 접근자 []

프로퍼티 접근자인 []는 타입으로 쓰일 때에도 동일하게 동작합니다. 하지만, obj['field']obj.field는 값이 동일하더라도 다른 타입을 가질 수 있기 때문에, 타입의 프로퍼티를 얻고자 한다면 반드시 첫 번째 방법을 사용해야 합니다.

const myName: NamedCylinder['name'] = 'alan';

이에 대한 내용은 아이템 14에서 더 자세히 다룹니다.

그 외에 두 공간 사이에서 다른 의미를 가지는 코드 패턴

  • this
    • 값으로 쓰일 때는 JS의 this 키워드
    • 타입으로 쓰일 때는 다형성 this라고 불리는 TS 타입. 서브클래스의 메서드 체인을 구현할 때 유용합니다.
  • &, |
    • 값으로 쓰일 때는 AND와 OR 비트연산
    • 타입으로 쓰일 때는 intersection과 union입니다.
  • const
    • const는 새 변수를 선언하는 키워드이지만,
    • as const는 리터럴 또는 리터럴 표현식의 추론된 타입을 바꿉니다.
  • extends
    • JS에서 그렇듯 서브클래스를 정의하는 데 사용되거나
    • TS 상에서 서브타입(interface A extends B) 또는 제너릭 타입의 한정자(Generic<T extends number>)를 정의할 수 있습니다.

이렇듯, 타입 공간과 값 공간에서 동일한 키워드로 사용되는 심벌들이 여럿 존재합니다. 그렇기 때문에 TS 코드가 본인이 의도한 대로 동작하지 않는다면, 타입 공간과 값 공간을 혼동하여 잘못 작성했을 가능성이 큽니다.