Javascript

TypeScript 심볼 완벽 분석: 타입 레벨에서의 심볼 활용과 고급 패턴

드리프트2 2025. 3. 15. 19:50

TypeScript 심볼 완벽 분석: 타입 레벨에서의 심볼 활용과 고급 패턴

이번 포스트에서는 TypeScript가 JavaScript 심볼을 타입 레벌에서 어떻게 다루는지 깊이 파헤쳐보겠습니다.

 

계산된 타입과 추론된 타입을 보여주기 위해 npm 패키지 asserttt를 사용합니다.

예시:

// 값의 타입 검증  
assertType<string>('abc');  
assertType<number>(123);  

// 타입 동등성 확인  
type Pair<T> = [T, T];  
type _ = Assert<Equal<  
  Pair<string>, [string, string]  
>>;  

심볼 타입

symboltypeof MY_SYMBOL

타입 추론은 일반적으로 다음과 같이 동작합니다:

  • let 선언: 더 넓고 일반적인 타입 추론
  • const 선언: 더 좁고 구체적인 타입 추론

예시:

let value1 = 123;  
assertType<number>(value1);  

const value2 = 123;  
assertType<123>(value2);  

 

이 규칙은 심볼에도 동일하게 적용됩니다:

let SYM1 = Symbol('SYM1');  
assertType<symbol>(SYM1);  

const SYM2 = Symbol('SYM2');  
assertType<typeof SYM2>(SYM2);  

 

typeof SYM2는 특정 심볼의 타입을 의미하는데요.

이 타입과 일치하는 새로운 값을 생성하는 것은 불가능합니다:

function f(_sym: typeof SYM2) {}  

f(SYM2); // 정상  
// @ts-expect-error: 'symbol' 타입은 'unique symbol'에 할당 불가  
f(Symbol('SYM2')); // 새로운 값!  

 

오류 메시지의 unique symbol에 대해서는 곧 설명하겠습니다.


주의: 타입 확장 문제

typeof SYM 타입을 다른 변수에 할당하면 symbol로 타입이 확장됩니다(const 선언에도 적용):

const SYM = Symbol('SYM');  

function getSym(): typeof SYM {  
  const X = SYM; // 타입: symbol  

  // @ts-expect-error: 'symbol'은 'unique symbol'에 할당 불가  
  return X;  
}  

 

관련 GitHub 이슈: "unique symbol lost on assignment to const despite type assertion"


unique symbol 타입

unique symbolsymbol의 하위 타입으로, 특정 위치에 고유한 심볼이 있음을 의미합니다.

const 변수 선언과 static readonly 프로퍼티에서 사용 가능하며, 다른 곳에서는 typeof S를 사용해야 합니다:

const SYM: unique symbol = Symbol('SYM');  

class MyClass {  
  static readonly SYM: unique symbol = Symbol('SYM');  
}  

 

위 코드는 다음 코드와 완전히 동일합니다:

const SYM = Symbol('SYM');  
class MyClass {  
  static readonly SYM = Symbol('SYM');  
}  

 

unique symbol읽기 전용 프로퍼티 타입으로도 선언됩니다.

예를 들어 Symbol.iterator의 타입 정의를 보면:

interface SymbolConstructor {  
  readonly iterator: unique symbol;  
}  

심볼 타입의 제한 사항

unique symbol 타입의 프로퍼티를 가진 객체는 생성할 수 없습니다:

type Obj = {  
  readonly sym: unique symbol;  
};  

const obj1: Obj = {  
  // @ts-expect-error: 'symbol'은 'unique symbol'에 할당 불가  
  sym: Symbol('SYM'),  
};  

심볼 기반 유니온 타입

심볼을 이용해 타입 안전한 유니온을 구성할 수 있습니다:

const ACTIVE = Symbol('ACTIVE');  
const INACTIVE = Symbol('INACTIVE');  
type ActSym = typeof ACTIVE | typeof INACTIVE;  

const activation1: ActSym = ACTIVE;  
const activation2: ActSym = INACTIVE;  
// @ts-expect-error: 'unique symbol'은 'ActSym'에 할당 불가  
const activation3: ActSym = Symbol('ACTIVE');  

 

문자열 유니온 타입과의 비교:

type ActStr = 'ACTIVE' | 'INACTIVE';  
const activation3: ActStr = 'ACTIVE'; // 허용됨 (타입 안전성 낮음)  

 

장단점:

  • ✅ 심볼: 타입 안전성 보장
  • ❌ 문자열: 값 비교로 인한 오류 가능성

특수 값으로서의 심볼

undefined/null 대신 심볼을 특수 값으로 사용하면 코드 가독성이 향상됩니다:

const EOF = Symbol('EOF');  
type StreamValue =  
  | typeof EOF  
  | string;  

심볼 기반 Enum 패턴

고유한 값을 가진 Enum을 생성할 때 유용합니다:

const Active = Symbol('Active');  
const Inactive = Symbol('Inactive');  

const Activation = {  
  __proto__: null,  
  Active,  
  Inactive,  
} as const;  

type ActivationType = PropertyValues<typeof Activation>;  

 

중간 변수 선언 이유:

  • 직접 객체 내부에 심볼 생성 시 타입이 symbol로 추론됨
  • 변수로 분리해야 정확한 타입(typeof Active | typeof Inactive) 유지