한 줄 정의
Lowercase는 문자열 리터럴 타입을 받아 모든 글자를 소문자로 바꾼 문자열 리터럴 타입을 돌려준다. .toLowerCase()의 타입 버전이다.
값이 아니라 타입을 다룬다. "MY_APP"이라는 값을 소문자로 바꾸는 게 아니라, "MY_APP"이라는 리터럴 타입을 소문자 리터럴 타입 "my_app"으로 바꾼다.
type Greeting = "Hello, world"
type QuietGreeting = Lowercase<Greeting>
// type QuietGreeting = "hello, world"
Uppercase·Capitalize·Uncapitalize와 함께 TypeScript가 제공하는 네 가지 내장 문자열 조작 타입 중 하나다.
작동 원리
Lowercase는 .d.ts 파일 어디에도 정의가 없다. Uppercase·Capitalize·Uncapitalize와 함께 컴파일러 내부에 직접 박혀 있는 컴파일러 내장(intrinsic) 타입이다. TypeScript 4.1부터는 이 내장 타입들이 자바스크립트의 문자열 런타임 함수를 그대로 호출해서 결과를 계산한다.
function applyStringMapping(symbol: Symbol, str: string) {
switch (intrinsicTypeKinds.get(symbol.escapedName as string)) {
case IntrinsicTypeKind.Uppercase: return str.toUpperCase()
case IntrinsicTypeKind.Lowercase: return str.toLowerCase()
case IntrinsicTypeKind.Capitalize: return str.charAt(0).toUpperCase() + str.slice(1)
case IntrinsicTypeKind.Uncapitalize: return str.charAt(0).toLowerCase() + str.slice(1)
}
return str
}
Lowercase 자리에 오면 컴파일러가 str.toLowerCase()를 호출한 결과를 그대로 리터럴 타입으로 박아 넣는다. 조건부 타입이나 infer로 짜여진 유틸리티가 아니라, 컴파일러가 문자열을 직접 계산해 넣는 특수 케이스라 성능이 좋고, 사용자가 흉내 낼 수 없다.
이 계산은 자바스크립트의 String.prototype.toLowerCase를 그대로 쓰기 때문에 로케일을 인식하지 않는다. 튀르키예어의 I/i 구분처럼 로케일마다 대소문자 규칙이 달라지는 언어에서는 예상과 다른 결과가 나올 수 있다.
템플릿 리터럴 타입 안에서 쓰면 문자열의 일부만 변형할 수 있다.
type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
type MainID = ASCIICacheKey<"MY_APP">
// type MainID = "id-my_app"
쓰는 법
리터럴 타입을 넣어야 결과도 리터럴 타입으로 나온다. string처럼 넓은 타입을 넣으면 좁혀지지 않고 그대로 string이 나온다.
type A = Lowercase<"HELLO"> // "hello"
type B = Lowercase<string> // string — 리터럴이 아니면 넓어지지 않는다
제네릭 함수에서 문자열 파라미터의 타입을 그대로 소문자로 매핑하는 데 흔히 쓴다.
function toId<S extends string>(key: S): Lowercase<S> {
return key.toLowerCase() as Lowercase<S>
}
const id = toId("USER_NAME") // 타입: "user_name"
실전 예제
이벤트 이름처럼 컨벤션이 대문자인 키를, 실제 핸들러 객체의 소문자 프로퍼티 키로 매핑할 때 Lowercase가 둘을 연결한다.
type EventName = 'CLICK' | 'HOVER' | 'FOCUS'
type Handlers = {
[K in EventName as Lowercase<K>]: (payload: string) => void
}
// {
// click: (payload: string) => void
// hover: (payload: string) => void
// focus: (payload: string) => void
// }
const handlers: Handlers = {
click: (payload) => console.log('clicked', payload),
hover: (payload) => console.log('hovered', payload),
focus: (payload) => console.log('focused', payload),
}
EventName에 새 이벤트를 추가하면 Handlers에도 소문자 키가 자동으로 생긴다. 매핑 규칙이 as Lowercase<K> 한 줄에 고정되어 있어, 대문자 이벤트 이름과 소문자 핸들러 키가 어긋날 자리가 없다.
쓰는 이유
Lowercase는 문자열 리터럴 타입과 템플릿 리터럴 타입이 실전에서 맞물리는 지점에 있다. 키 컨벤션을 변환하는 매핑 타입, CSS-in-JS의 속성 이름 정규화, 라우트 세그먼트나 이벤트 이름처럼 대소문자 규칙이 서로 다른 두 체계를 연결하는 타입은 전부 Uppercase·Lowercase·Capitalize·Uncapitalize 중 하나를 쓴다. 컴파일러 내장이라 사용자 코드로는 똑같은 걸 만들 수 없고, 문자열을 타입 수준에서 실제로 변형해야 하는 자리에는 이 넷이 유일한 통로다. Lowercase 하나를 정확히 알면 나머지 셋은 같은 메커니즘의 변주로 바로 읽힌다.
Reference