Template Literal Types — 문자열 패턴으로 타입 만들기
Template Literal Type은 JavaScript의 템플릿 리터럴 문법을 타입 레벨 에서 사용하는 것입니다.
기본 문법
// 문자열 리터럴을 조합해서 새 타입 생성
type Greeting = `hello ${string}`;
const a: Greeting = 'hello world'; // OK
const b: Greeting = 'hello TypeScript'; // OK
// const c: Greeting = 'hi world'; // ❌ Error
// 유니온과 조합하면 모든 경우의 수가 생성됨
type Color = 'red' | 'blue' | 'green';
type Size = 'small' | 'large';
type ColorSize = `${Color}-${Size}`;
// 'red-small' | 'red-large' | 'blue-small' | 'blue-large' | 'green-small' | 'green-large'
유니온끼리 조합하면 곱집합 이 만들어집니다. 3가지 색상 x 2가지 크기 = 6가지 조합.
내장 문자열 변환 유틸리티
type Upper = Uppercase<'hello'>; // 'HELLO'
type Lower = Lowercase<'HELLO'>; // 'hello'
type Cap = Capitalize<'hello'>; // 'Hello'
type Uncap = Uncapitalize<'Hello'>; // 'hello'
// 유니온에도 분배됨
type Events = 'click' | 'scroll' | 'keypress';
type OnEvents = `on${Capitalize<Events>}`;
// 'onClick' | 'onScroll' | 'onKeypress'
실전 패턴
CSS 속성 타입
type CSSUnit = 'px' | 'em' | 'rem' | '%' | 'vh' | 'vw';
type CSSValue = `${number}${CSSUnit}`;
const width: CSSValue = '100px'; // OK
const height: CSSValue = '50vh'; // OK
// const wrong: CSSValue = '100'; // ❌ Error — 단위 없음
// const wrong2: CSSValue = 'auto'; // ❌ Error — 패턴 불일치
이벤트 핸들러 이름 생성
type EventName = 'click' | 'focus' | 'blur' | 'change';
// on + 대문자 시작 이벤트 이름
type EventHandler = `on${Capitalize<EventName>}`;
// 'onClick' | 'onFocus' | 'onBlur' | 'onChange'
// 핸들러 맵 생성
type EventHandlerMap = {
[K in EventName as `on${Capitalize<K>}`]: (event: Event) => void;
};
// {
// onClick: (event: Event) => void;
// onFocus: (event: Event) => void;
// onBlur: (event: Event) => void;
// onChange: (event: Event) => void;
// }
API 라우트 타입
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type Endpoint = '/users' | '/posts' | '/comments';
type ApiRoute = `${HttpMethod} ${Endpoint}`;
// 'GET /users' | 'GET /posts' | 'GET /comments' |
// 'POST /users' | 'POST /posts' | 'POST /comments' | ...
infer와 조합 — 문자열 파싱
// 점 표기법에서 첫 번째 키 추출
type FirstKey<S extends string> = S extends `${infer K}.${string}` ? K : S;
type A = FirstKey<'user.name.first'>; // 'user'
type B = FirstKey<'name'>; // 'name'
// 점 표기법에서 나머지 추출
type RestKeys<S extends string> = S extends `${string}.${infer R}` ? R : never;
type C = RestKeys<'user.name.first'>; // 'name.first'
깊은 속성 접근 타입
// 점 표기법으로 깊은 속성 타입을 추출
type DeepGet<T, Path extends string> =
Path extends `${infer K}.${infer Rest}`
? K extends keyof T
? DeepGet<T[K], Rest>
: never
: Path extends keyof T
? T[Path]
: never;
interface Config {
db: {
host: string;
port: number;
};
app: {
name: string;
};
}
type DbHost = DeepGet<Config, 'db.host'>; // string
type AppName = DeepGet<Config, 'app.name'>; // string
카멜케이스를 케밥케이스로 변환
type CamelToKebab<S extends string> =
S extends `${infer C}${infer Rest}`
? C extends Uppercase<C>
? `-${Lowercase<C>}${CamelToKebab<Rest>}`
: `${C}${CamelToKebab<Rest>}`
: S;
type A = CamelToKebab<'backgroundColor'>; // 'background-color'
type B = CamelToKebab<'fontSize'>; // 'font-size'
공부하다 보니 타입 레벨에서 문자열을 파싱하는 게 꽤 강력하더라고요. 타입 챌린지에서도 자주 출제되는 유형입니다.
Replace 타입
// 첫 번째 매칭만 교체
type Replace<
S extends string,
From extends string,
To extends string
> = From extends ''
? S
: S extends `${infer Before}${From}${infer After}`
? `${Before}${To}${After}`
: S;
type A = Replace<'hello world', 'world', 'TS'>; // 'hello TS'
// 모든 매칭 교체
type ReplaceAll<
S extends string,
From extends string,
To extends string
> = From extends ''
? S
: S extends `${infer Before}${From}${infer After}`
? ReplaceAll<`${Before}${To}${After}`, From, To>
: S;
정리
- Template Literal Type은 문자열 패턴을 타입으로 표현한다
- 유니온과 조합하면 모든 경우의 수(곱집합)가 생성된다
Uppercase,Lowercase,Capitalize,Uncapitalize내장 유틸리티가 있다infer와 조합하면 문자열을 파싱하고 변환할 수 있다- CSS 값, 이벤트 핸들러, API 라우트 등 실전에서 다양하게 활용된다
댓글 로딩 중...