TypeScript 심볼 완벽 분석: 타입 레벨에서의 심볼 활용과 고급 패턴
이번 포스트에서는 TypeScript가 JavaScript 심볼을 타입 레벌에서 어떻게 다루는지 깊이 파헤쳐보겠습니다.
계산된 타입과 추론된 타입을 보여주기 위해 npm 패키지 asserttt를 사용합니다.
예시:
// 값의 타입 검증
assertType<string>('abc');
assertType<number>(123);
// 타입 동등성 확인
type Pair<T> = [T, T];
type _ = Assert<Equal<
Pair<string>, [string, string]
>>;
심볼 타입
symbol
과 typeof 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 symbol
은 symbol
의 하위 타입으로, 특정 위치에 고유한 심볼이 있음을 의미합니다.
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
) 유지
'Javascript' 카테고리의 다른 글
TypeScript에서의 Array 타입 표기법: T[] vs. Array<T> 완벽 분석 (0) | 2025.03.19 |
---|---|
Express.js 완벽 마스터하기: 초보자도 쉽게 배우는 미들웨어, next 메커니즘, 라우팅의 기초부터 활용까지 (0) | 2025.03.17 |
TypeScript 조건부 타입 완벽 가이드: 유니온 타입과 유틸리티 타입 활용의 모든 것 (0) | 2025.03.15 |
TypeScript Mapped Types 완벽 정복: 기본부터 고급 활용까지 (0) | 2025.03.13 |
TypeScript의 infer 키워드로 복합 타입에서 원하는 부분만 깔끔하게 추출하기 (0) | 2025.03.13 |