[TypeScript] Template Literal Types
TypeScript Template Literal Types, TS Template Literal Types, TypeScript string manipulation, Uppercase, Lowercase, Capitalize, Uncapitalize, template string types
정의
Template Literal Type 은 TypeScript 4.1 에서 도입된 문법으로, JavaScript template literal 처럼 백틱과 ${...} 를 이용해 타입 레벨 에서 문자열을 조합/조작합니다. Union 분배, 내장 문자열 utility, mapped type / conditional type 조합으로 강력한 정적 계산이 가능합니다.
기본
type World = "world";
type Greeting = `hello ${World}`; // "hello world"
type Cardinal = "north" | "south" | "east" | "west";
type Quadrant = `${Cardinal}-${Cardinal}`;
// = "north-north" | "north-south" | ... 총 16개
Union 안에서 자동 분배 (cartesian product).
내장 문자열 utility
Uppercase<S>Lowercase<S>Capitalize<S>: 첫 글자 대문자Uncapitalize<S>: 첫 글자 소문자
type A = Uppercase<"hello">; // "HELLO"
type B = Lowercase<"HELLO">; // "hello"
type C = Capitalize<"hello">; // "Hello"
type D = Uncapitalize<"Hello">; // "hello"
Event Handler 이름
type EventName<T extends string> = `on${Capitalize<T>}`;
type A = EventName<"click">; // "onClick"
type B = EventName<"mouseover">; // "onMouseover"
Mapped 와 조합:
type EventHandlers<E extends string> = {
[K in E as `on${Capitalize<K>}`]: (e: Event) => void;
};
type Handlers = EventHandlers<"click" | "hover" | "submit">;
// = {onClick: (e: Event) => void; onHover: ...; onSubmit: ...}
URL / Route 타이핑
type Route = `/users/${string}` | `/posts/${number}`;
const r1: Route = "/users/kim"; // OK
const r2: Route = "/posts/42"; // OK
const r3: Route = "/comments/1"; // 오류
Path parameter 추출
type ExtractParams<S extends string> =
S extends `${string}:${infer P}/${infer R}`
? P | ExtractParams<R>
: S extends `${string}:${infer P}`
? P
: never;
type A = ExtractParams<"/users/:id/posts/:postId">;
// = "id" | "postId"
type RouteParams<S extends string> = {
[K in ExtractParams<S>]: string;
};
type P = RouteParams<"/users/:id/posts/:postId">;
// = {id: string; postId: string}
Type-safe routing 의 기반.
Case conversion
SnakeToCamel
type SnakeToCamel<S extends string> =
S extends `${infer H}_${infer R}`
? `${H}${Capitalize<SnakeToCamel<R>>}`
: S;
type A = SnakeToCamel<"user_id">; // "userId"
type B = SnakeToCamel<"created_at_utc">; // "createdAtUtc"
CamelToSnake
type CamelToSnake<S extends string> =
S extends `${infer H}${infer T}`
? T extends Uncapitalize<T>
? `${Uncapitalize<H>}${CamelToSnake<T>}`
: `${Uncapitalize<H>}_${CamelToSnake<T>}`
: S;
type A = CamelToSnake<"userId">; // "user_id"
type B = CamelToSnake<"getName">; // "get_name"
KebabCase, ScreamingSnake 등
type-fest 라이브러리에 이미 정의되어 있음. 재발명하기보다는 lookup.
Numeric parsing
type ParseInt<S extends string> =
S extends `${infer N extends number}` ? N : never;
type A = ParseInt<"42">; // 42
type B = ParseInt<"3.14">; // 3.14
type C = ParseInt<"foo">; // never
TypeScript 4.8+ 에서 ${infer N extends number} 문법으로 문자열을 숫자로 파싱 가능.
String Split
type Split<S extends string, D extends string> =
S extends `${infer H}${D}${infer R}` ? [H, ...Split<R, D>] : [S];
type A = Split<"a,b,c", ",">; // ["a", "b", "c"]
type B = Split<"1.2.3", ".">; // ["1", "2", "3"]
String Join
type Join<T extends string[], D extends string> =
T extends [] ? "" :
T extends [infer F extends string] ? F :
T extends [infer F extends string, ...infer R extends string[]]
? `${F}${D}${Join<R, D>}`
: never;
type A = Join<["a", "b", "c"], "-">; // "a-b-c"
실전: CSS unit
type CssUnit = "px" | "em" | "rem" | "%" | "vh" | "vw";
type CssValue = `${number}${CssUnit}` | `${number}` | "auto" | "inherit";
const v1: CssValue = "10px"; // OK
const v2: CssValue = "auto"; // OK
const v3: CssValue = "10bar"; // 오류
실전: Environment variable
type EnvKey = `NEXT_PUBLIC_${string}`;
function getEnv<K extends EnvKey>(key: K): string | undefined {
return process.env[key];
}
getEnv("NEXT_PUBLIC_API_URL"); // OK
getEnv("DATABASE_URL"); // 오류
실전: SQL 유사
type SqlWhere<T> = {
[K in keyof T]?: T[K] | `${string}${T[K] extends string ? "LIKE" : ""}${string}`;
};
// ... 실용성 낮음, 예시 정도
Distributive with template literal
type Prefix<S extends string> = `prefix_${S}`;
type A = Prefix<"a" | "b" | "c">; // "prefix_a" | "prefix_b" | "prefix_c"
Union 이 자동 분배되어 각 요소에 대해 template 적용.
Cartesian product
type Combinations<A extends string, B extends string> = `${A}-${B}`;
type A = Combinations<"foo" | "bar", "1" | "2">;
// = "foo-1" | "foo-2" | "bar-1" | "bar-2"
함정
WARNING
재귀 depth 제한 (50 정도). 매우 긴 문자열 파싱은 실패.
CAUTION
${string} 은 모든 문자열 매칭. Loose 함. ${number} 로 명시하는 편이 안전.
WARNING
Capitalize<T> 는 T 가 string 이어야 함. Capitalize<string & K> 로 좁힘.
IMPORTANT
성능. 대규모 template literal type 은 컴파일 시간 급증. Alias 로 캐시.
CAUTION
Runtime 에는 없음. Template literal type 은 컴파일 시만. 실제 문자열 조작은 JavaScript 로.
관련 위키
- TypeScript - 상위 개요
- Conditional Types - Template + conditional
- Mapped Types - 키 조작
- Utility Types - Uppercase, Lowercase, …
- Generics
- Type Aliases
- Type Narrowing
이 글의 용어 (7개)
- [TypeScript] Conditional Typestypescript
- 정의 Conditional Type 은 형태로 타입 레벨에서 조건 분기를 표현합니다. 키워드로 매칭된 타입을 추출하고, union 에 대해서는 자동 분배 (distributive…
- [TypeScript] Genericstypescript
- 정의 Generics 는 타입을 파라미터화하는 문법입니다. 함수, 클래스, 인터페이스, type alias 가 재사용 가능 하면서도 타입 안전 하도록 만듭니다. Java 의 ge…
- [TypeScript] Mapped Typestypescript
- 정의 Mapped Type 은 기존 타입의 각 필드를 순회 ( ) 하며 새 타입을 생성하는 문법입니다. , modifier 를 붙이거나 제거 ( , ) 하고, clause 로 키…
- [TypeScript] Type Aliasestypescript
- 정의 Type Alias 는 타입에 이름을 붙이는 TypeScript 문법 ( ). Interface 는 객체 shape 만 표현하지만, type alias 는 모든 타입 표현식…
- [TypeScript] Type Narrowing (Type Guards)typescript
- 정의 Type Narrowing 은 TypeScript 컴파일러가 제어 흐름 분석 (control flow analysis) 을 통해 특정 위치에서 값의 타입을 더 좁게 추론하는…
- [TypeScript] Utility Typestypescript
- 정의 Utility Types 는 TypeScript 표준 라이브러리에 포함된 미리 정의된 generic 타입 들입니다. 기존 타입을 변형/구성하는 흔한 패턴을 제공하며, map…
- TypeScripttypescript
- 정의 TypeScript 는 Microsoft 가 2012년 발표한 JavaScript 의 상위 집합 프로그래밍 언어입니다. 정적 타입 시스템, 인터페이스, 제네릭, enum, …
💬 댓글