-
TypeScript에서 (string & {}) 을 제외한 리터럴 유니언 타입만 남기기 🫥TypeScript 2025. 5. 31. 23:53
TypeScript를 쓰다 보면 아래처럼 리터럴 유니언 타입을 정의하는 경우가 많다.
type MyType = 'a' | 'b' | (string & {});그런데, 여기서 진짜 원하는 건 'a' | 'b' 만 남기고 string & {}은 제거하는 것일 수 있다. (더 엄격하게 타입을 체크하기 위해서)
더 구체적으로는 다음과 같은 예제를 들 수 있다.
type MyType = 'a' | 'b' | (string & {}); type NoStringMyTime = 'a' | 'b'; const data: MyType = 'a' function dataParse(value: NoStringMyTime) { console.log(value); } // Argument of type '(string & {}) | "a"' is not assignable to parameter of type 'NoStringMyTime'. dataParse(data);그럼 어떻게 string & {}을 제거해서 'a' | 'b'만 남길 수 있을까?
이 문제는 단순한 유틸 타입으로 해결되지 않는다.그럼에도 불구하고, TypeScript의 분산 조건부 타입(distributive conditional types)을 응용하면 원하는 타입 유틸을 만들 수 있다.
✅ 목표: 'a' | 'b' | (string & {}) 에서 string을 제거해서 'a' | 'b'만 남기기
🔬 솔루션> 넓은 타입 필터링 유틸리티 만들기
type LiteralOnly<T> = T extends string ? string extends T ? never : T : T;해당 타입 유틸을 사용하면 이 문제를 해결할 수 있다.
type MyType = 'a' | 'b' | (string & {}); type NoStringMyTime = 'a' | 'b'; type LiteralOnly<T> = T extends string ? string extends T ? never : T : T; const data: LiteralOnly<MyType> = 'a' function dataParse(value: NoStringMyTime) { console.log(value); } dataParse(data); // 에러 없음🔍 작동 방식 설명
type LiteralOnly<T> = T extends string ? string extends T ? never : T : T; const data: LiteralOnly<MyType> = 'a'구체적으로 어떻게 동작하는지 확인해보자.
- T extends string: 일단 T가 string 계열인지 확인하고,
- string extends T: 이게 진짜 string 전체인지 판단한다.
- string extends string → (넓은 string이 맞음) ✅
- 'a' extends string → (리터럴도 string이니까) ✅
- 하지만 'a'는 string의 슈퍼셋은 아니기 때문에 string extends 'a' → ❌
즉, string extends T 가 참인 경우는 T가 진짜로 string일 때만 해당된다.
그래서 그 경우는 never로 제거해주고, 'a' | 'b' 같은 리터럴들은 남게된다.💡 추가 예제 코드들
type A = LiteralOnly<'a' | 'b' | (string & {})>; // 'a' | 'b' type B = LiteralOnly<'x' | 'y'>; // 'x' | 'y' type C = LiteralOnly<string>; // never type D = LiteralOnly<'z' | number>; // 'z' | number (string만 제거됨)즉, string만 똑 떼어낼 수 있다.
number, boolean 등은 그대로 남는다.
🛠 그렇다면, string 말고 다른 넓은 타입도 제거하려면?
type RemoveWider<T, W> = T extends W ? W extends T ? never : T : T; type OnlyLiterals = RemoveWider<'a' | 'b' | (string & {}) | number, string | number>; // 결과: 'a' | 'b'이렇게 하면 string과 number 모두 제거할 수 있다.
✨ 요약하자면,
TypeScript는 정적 타입 시스템이면서도 엄청나게 유연하고 강력한 것 같다.
이번 예제처럼 "넓은 타입을 제거하고 리터럴만 남기기" 같은 니치하지만 실무에선 꽤 유용할 수 있는 유틸도 만들 수 있었다.
기존의 타입들을 최대한 활용할 수 있도록 유틸을 잘 작성하는 건 중요하다고 느끼는데, 이런 유틸 타입들에 대한 경험을 쌓아 나가는 것은 재밌는 일 같다 💘
📎 참고
- 해당 코드의 영감이 된 StackOverflow 원문: Is it possible to remove a wider type from a literal union in TypeScript?
- TypeScript Handbook: Conditional Types
'TypeScript' 카테고리의 다른 글
TypeScript의 assert 함수에 대해 ⏰ (0) 2025.08.29 TypeScript 빌드 후 .d.ts 파일이 생성되지 않는 문제 ⁉️ (0) 2025.06.10 TypeScript에서 __brand 패턴 알아보기 ✈️ (타입 안정성 높이기) (0) 2025.03.30 TypeScript에서 객체 타입 비교 및 extends 활용하기 🏘️ (0) 2025.03.21 [typescript] Record를 사용하여 객체 Key 타입 설정하기 🍎 (0) 2024.11.01