본문 바로가기

Deep Dive

[TypeScript] String Type 구조 탐구해보기

 

 

타입스크립트 강의를 듣다가 String Type은 다른 타입들에 비해 조금 더 세분화되어 있다고 느꼈고,

정확히 어떻게 구분되어 있는지 궁금해서 찾아보게 되었다.

 

 


 

 

 

Typescript의 String Type은 대략 아래와 같이 유추할 수 있다.

 

* 기준 : "포괄적인 타입에서 구체적인 타입으로 가는" 범위

string (최상위 타입)
├── template literal types / union of string literals (조합을 통한 타입 확장)
└── string literal types (최하위 타입)

 

위 계층 구조에서 string을 최상위로, string literal types를 최하위로 보는 건 일반적인 이해를 돕기 위한 표현으로는 괜찮지만,

엄밀히 말해 TypeScript에서는 이러한 계층 구조가 완전히 고정된 순서로 성립되지는 않는다. (어쩐지 계속 찾아봐도 나오지 않았다.)

 

cf. 이해를 돕기 위한 개념적인 모델(도서에서 흔히 볼 수 있는 멘탈 모델..)이므로 실제로 이렇게 작동된다는 것은 아니다.

 

 

 

✏️ 각 타입 구조 설명

 

1. string (최상위): 가장 넓은 타입으로, 모든 문자열을 포함하기 때문에 최상위에 위치한다.

 

2. union of string literals와 template literal types: 이 둘은 string과 특정 리터럴 타입 사이에 위치하는 병렬적인 관계이며, 다른 리터럴 타입을 조합해 새로운 타입을 생성하는 방식에 가깝다.

 

3. union of string literals : 여러 개의 문자열 리터럴 타입을 묶어 만드는 유니온 타입. 예를 들어 "hello" | "world" 같은 형태.

 

4. template literal types : 기존의 리터럴 타입을 조합해 새 문자열을 생성하는 방식. 백틱과 정규표현식 등 다양한 조합을 통해 타입 확장 가능. 문자열 조작을 타입 레벨에서 가능하게 하는 TypeScript의 고유 기능이다.

type Greeting = `Hello ${string}`;  // template literal types
type GreetingUnion = "Hello World" | "Hello TypeScript";  // union of string literals

// 둘은 서로 다른 접근 방식으로 타입을 표현
const a: Greeting = "Hello anything";        // OK
const b: GreetingUnion = "Hello anything";  // Error

 

5. string literal types (최하위): 가장 구체적인 형태의 타입이므로 "최하위 타입"이라고 볼 수 있지만, 특정 string 값만을 표현하는 고정된 타입일 뿐, 상속 구조에서의 하위 타입과는 다르다. 특정 문자열 값으로 정의된 타입들. 예를 들어 "hello", "world" 같은 리터럴 타입들이 여기에 포함된다. 또한, const assertions(as const)와 함께 자주 사용된다.

 

 

 

✏️ Q&A

 

Q. 여기서 궁금증이 생겼다. 그러면 다른 타입들도 string 타입같이 계층적 구조를 갖추고 있을까?

 

- TypeScript에서는 다른 기본 타입들도 비슷한 구조를 가질 수 있지만, string 타입이 특히 다양한 형태로 사용된다.

예를 들어, number와 boolean 타입도 리터럴 타입을 가질 수 있지만, template literal types과 같은 특수 타입은 string에만 적용된다.

TypeScript 타입 시스템이 복잡한 계층 구조를 갖추고 있지만, string처럼 리터럴 조합을 활용한 계층적 표현은 특정 타입에 제한적으로 적용된다.

 

 

 

Q. 그럼 포괄적인 관계에서 구체적인 관계로 간다고 했는데, 이것은 공변성(CoVariance)에 해당되는가?

 

1. 공변성은 일반적으로 하위 타입이 상위 타입을 대체할 수 있는 관계를 말한다.

// 공변성 예시
type StringLiteral = 'hello';
let strLiteral: StringLiteral = 'hello';
let str: string = strLiteral;  // OK (공변성)

// 반대는 불가능
let str2: string = 'world';
let strLiteral2: StringLiteral = str2;  // Error

 

 

2. TypeScript에서 공변성은 다음과 같은 원칙으로 작동한다:

- 넓은 타입 → 좁은 타입으로 할당 가능: 예를 들어, string 타입은 "hello" 같은 string literal type을 포함하므로 string은 리터럴 타입의 상위 개념이 된다. 이때 "hello"를 string으로 취급하는 것은 공변성에 해당할 수 있음.

- 그러나 template literal types와 union of string literals은 공변성을 통한 관계라기보다는 주어진 리터럴 타입의 조합을 통해 확장된 타입이다.

- 예를 들어 "a" | "b"와 같은 유니온 타입은 개별 리터럴의 상위 타입이 아니며, template literal types 또한 공변성이 적용되지 않음.

(공변성.. 참 어럽다..ㅠㅠ 하하)

 

 

3. 정리하자면,

- string → string literal types로 내려갈수록 더 구체적인 타입이 된다. 이를 공변성이라고 할 수 있지만, template literal types나 union of string literals은 공변성 관계라기보다는 리터럴 타입 조합의 범위 확장으로 보는 것이 알맞다.

- 해당 구조는 타입의 포괄성과 표현력 차이에 따른 구조로 이해하는 것이 더 적합하다.

 

 


 

 

이 부분에 대해 궁금해서 찾아봤는데, 내가 원하는 내용의 포스트를 찾을 수 없어서 여러 자료를 참고하여 정리해 보았다.

 

혹시 이 부분에 대해 더 깊이 있는 지식을 가지고 계신 분들이 있다면, 댓글로 의견을 나눠주시면 감사하겠습니다. 🌞