본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout

내비게이션

  • Tech
  • Projects
  • Books
  • About
  • Tags

카테고리

  • AI / ML
  • 웹 개발
  • 프로그래밍
  • 개발 도구

연결

  • GitHub
  • Email
  • RSS
© 2026 Kreath Archive. All rights reserved.Built with Next.js + MDX
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 7장: 템플릿 리터럴 타입으로 문자열 다루기
2026년 2월 3일·프로그래밍·

7장: 템플릿 리터럴 타입으로 문자열 다루기

TypeScript 템플릿 리터럴 타입의 원리, 내장 문자열 조작 유틸리티, 패턴 매칭, 그리고 매핑 타입과의 결합을 통한 실전 패턴을 다룹니다.

12분802자6개 섹션
typescriptperformancedevtools
공유
typescript-deepdive7 / 12
123456789101112
이전6장: 매핑 타입과 키 재매핑 고급 패턴다음8장: infer 키워드와 타입 추론 마스터

6장에서 매핑 타입과 키 재매핑을 배웠습니다. 이 장에서는 타입 수준에서 문자열을 조작하는 템플릿 리터럴 타입(Template Literal Types) 을 다룹니다. TypeScript 4.1에서 도입된 이 기능은 문자열 패턴을 타입으로 표현하고, 문자열 조합을 자동으로 생성하며, 매핑 타입과 결합하여 강력한 타입 안전 API를 설계하는 데 핵심적인 역할을 합니다.

템플릿 리터럴 타입의 기본

JavaScript의 템플릿 리터럴과 동일한 구문을 타입 수준에서 사용합니다.

기본 구문
typescript
type Greeting = `Hello, ${string}`;
 
const a: Greeting = "Hello, World";   // OK
const b: Greeting = "Hello, TypeScript"; // OK
const c: Greeting = "Hi, World";       // Error: 'Hi, World'는 'Hello, ${string}'에 할당 불가

리터럴 타입을 조합하면 모든 가능한 문자열 조합이 자동으로 생성됩니다.

유니온 조합
typescript
type Color = "red" | "green" | "blue";
type Size = "sm" | "md" | "lg";
 
type ColorSize = `${Color}-${Size}`;
// "red-sm" | "red-md" | "red-lg" |
// "green-sm" | "green-md" | "green-lg" |
// "blue-sm" | "blue-md" | "blue-lg"
 
// CSS 클래스 이름 생성
type Spacing = 0 | 1 | 2 | 4 | 8;
type Direction = "t" | "r" | "b" | "l" | "x" | "y";
type SpacingClass = `p${Direction}-${Spacing}` | `m${Direction}-${Spacing}`;
// "pt-0" | "pt-1" | ... | "ml-8" — 총 60개 조합
Warning

유니온 타입의 조합은 곱집합으로 생성됩니다. A가 10개, B가 10개, C가 10개인 유니온을 결합하면 ${A}-${B}-${C}는 1,000개의 멤버를 가진 유니온이 됩니다. 지나치게 큰 유니온은 컴파일 성능에 영향을 줄 수 있으니 주의하세요.

내장 문자열 조작 유틸리티

TypeScript는 네 가지 내장 문자열 조작 유틸리티 타입을 제공합니다.

내장 문자열 유틸리티
typescript
type A = Uppercase<"hello">;     // "HELLO"
type B = Lowercase<"HELLO">;     // "hello"
type C = Capitalize<"hello">;    // "Hello"
type D = Uncapitalize<"Hello">;  // "hello"
 
// 유니온에도 분배 적용
type Words = "hello" | "world" | "typescript";
type Upper = Uppercase<Words>;   // "HELLO" | "WORLD" | "TYPESCRIPT"

이 유틸리티들은 컴파일러에 내장되어 있어 조건부 타입으로 구현할 수 없는 수준의 변환을 제공합니다.

패턴 매칭과 infer

템플릿 리터럴 타입에서 infer를 사용하면 문자열의 특정 부분을 추출할 수 있습니다. 이는 런타임의 정규식과 유사한 패턴 매칭을 타입 수준에서 수행합니다.

문자열 패턴 매칭
typescript
// 첫 글자 추출
type FirstChar<S extends string> =
  S extends `${infer C}${infer _}` ? C : never;
 
type FC = FirstChar<"hello">;  // "h"
 
// 마지막 글자 추출
type LastChar<S extends string> =
  S extends `${infer _}${infer Rest}`
    ? Rest extends "" ? _ : LastChar<Rest>
    : never;
 
type LC = LastChar<"hello">;  // "o"
 
// 구분자로 분할
type Split<S extends string, D extends string> =
  S extends `${infer Head}${D}${infer Tail}`
    ? [Head, ...Split<Tail, D>]
    : [S];
 
type Parts = Split<"a.b.c.d", ".">;  // ["a", "b", "c", "d"]

문자열 변환 유틸리티

커스텀 문자열 변환
typescript
// camelCase → kebab-case
type CamelToKebab<S extends string> =
  S extends `${infer C}${infer Rest}`
    ? C extends Uppercase<C>
      ? `-${Lowercase<C>}${CamelToKebab<Rest>}`
      : `${C}${CamelToKebab<Rest>}`
    : S;
 
type Kebab = CamelToKebab<"backgroundColor">;  // "background-color"
type Kebab2 = CamelToKebab<"fontSize">;         // "font-size"
 
// kebab-case → camelCase
type KebabToCamel<S extends string> =
  S extends `${infer Head}-${infer Tail}`
    ? `${Head}${KebabToCamel<Capitalize<Tail>>}`
    : S;
 
type Camel = KebabToCamel<"background-color">;  // "backgroundColor"
type Camel2 = KebabToCamel<"font-size">;         // "fontSize"

문자열 치환

타입 수준 문자열 치환
typescript
type Replace<
  S extends string,
  From extends string,
  To extends string
> = S extends `${infer Before}${From}${infer After}`
  ? `${Before}${To}${After}`
  : S;
 
type ReplaceAll<
  S extends string,
  From extends string,
  To extends string
> = S extends `${infer Before}${From}${infer After}`
  ? ReplaceAll<`${Before}${To}${After}`, From, To>
  : S;
 
type R1 = Replace<"hello world", " ", "-">;      // "hello-world"
type R2 = ReplaceAll<"a.b.c.d", ".", "/">;        // "a/b/c/d"

실전 패턴

타입 안전 CSS 프로퍼티

CSS 프로퍼티 타입 시스템
typescript
type CSSProperty =
  | "margin" | "padding" | "border"
  | "font" | "background" | "color";
 
type CSSDirection = "top" | "right" | "bottom" | "left";
 
type CSSCompound = CSSProperty | `${CSSProperty}-${CSSDirection}`;
 
function setStyle(element: HTMLElement, property: CSSCompound, value: string) {
  // ...
}
 
setStyle(document.body, "margin-top", "10px");    // OK
setStyle(document.body, "padding-left", "20px");  // OK
setStyle(document.body, "margin-center", "10px"); // Error

타입 안전 라우트 파라미터

URL 패턴에서 파라미터를 자동으로 추출하는 타입입니다.

라우트 파라미터 추출
typescript
type ExtractParams<Path extends string> =
  Path extends `${string}:${infer Param}/${infer Rest}`
    ? { [K in Param | keyof ExtractParams<Rest>]: string }
    : Path extends `${string}:${infer Param}`
      ? { [K in Param]: string }
      : {};
 
type Params1 = ExtractParams<"/users/:id">;
// { id: string }
 
type Params2 = ExtractParams<"/users/:userId/posts/:postId">;
// { userId: string; postId: string }
 
function createRoute<const Path extends string>(
  path: Path,
  handler: (params: ExtractParams<Path>) => void
) {
  // 라우트 등록 로직...
}
 
createRoute("/users/:id", (params) => {
  console.log(params.id);       // OK — 자동완성 지원
  console.log(params.name);     // Error — 'name'은 존재하지 않음
});

타입 안전 SQL 쿼리 빌더

SELECT 쿼리 컬럼 추출
typescript
type ExtractColumns<SQL extends string> =
  SQL extends `SELECT ${infer Columns} FROM ${string}`
    ? Split<Trim<Columns>, ",">
    : never;
 
type Trim<S extends string> =
  S extends ` ${infer Rest}` ? Trim<Rest> :
  S extends `${infer Rest} ` ? Trim<Rest> :
  S;
 
type TrimAll<T extends string[]> = {
  [K in keyof T]: Trim<T[K] & string>;
};
 
// 실제 활용에서는 더 정교한 파싱이 필요하지만,
// 기본 개념을 보여주는 예시입니다
type Columns = ExtractColumns<"SELECT id, name, email FROM users">;
// ["id", " name", " email"] — Trim 적용 필요

점 표기법 경로 타입

6장에서 다룬 PathValue의 역방향으로, 객체 타입에서 가능한 모든 경로를 생성하는 타입입니다.

객체 경로 자동 생성
typescript
type Paths<T, Prefix extends string = ""> = T extends object
  ? {
      [K in keyof T & string]: T[K] extends object
        ? `${Prefix}${K}` | Paths<T[K], `${Prefix}${K}.`>
        : `${Prefix}${K}`;
    }[keyof T & string]
  : never;
 
interface Config {
  server: {
    port: number;
    host: string;
  };
  database: {
    url: string;
    pool: {
      min: number;
      max: number;
    };
  };
}
 
type ConfigPaths = Paths<Config>;
// "server" | "server.port" | "server.host" |
// "database" | "database.url" | "database.pool" |
// "database.pool.min" | "database.pool.max"
 
function getConfig<P extends ConfigPaths>(path: P): PathValue<Config, P> {
  // 구현...
}
 
const port = getConfig("server.port");      // number
const dbUrl = getConfig("database.url");    // string

타입 안전 이벤트 이름

이벤트 이름 패턴
typescript
type DOMEventMap = {
  click: MouseEvent;
  dblclick: MouseEvent;
  keydown: KeyboardEvent;
  keyup: KeyboardEvent;
  focus: FocusEvent;
  blur: FocusEvent;
};
 
type PrefixedEvents<
  T extends Record<string, any>,
  Prefix extends string
> = {
  [K in keyof T as `${Prefix}${Capitalize<string & K>}`]: T[K];
};
 
type OnEvents = PrefixedEvents<DOMEventMap, "on">;
// {
//   onClick: MouseEvent;
//   onDblclick: MouseEvent;
//   onKeydown: KeyboardEvent;
//   onKeyup: KeyboardEvent;
//   onFocus: FocusEvent;
//   onBlur: FocusEvent;
// }
 
// React 스타일 이벤트 핸들러 타입 생성
type EventHandlerProps<T extends Record<string, any>> = {
  [K in keyof T as `on${Capitalize<string & K>}`]?: (event: T[K]) => void;
};
 
type DOMEventHandlers = EventHandlerProps<DOMEventMap>;
// {
//   onClick?: (event: MouseEvent) => void;
//   onDblclick?: (event: MouseEvent) => void;
//   ...
// }

매핑 타입과의 결합 패턴 종합

이전 장에서 배운 매핑 타입과 템플릿 리터럴 타입을 결합하면, 기존 타입에서 완전히 새로운 API 타입을 자동 생성할 수 있습니다.

Store 패턴 자동 생성
typescript
interface State {
  count: number;
  name: string;
  items: string[];
}
 
// State에서 자동으로 getter, setter, action 타입 생성
type StoreGetters<T> = {
  [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
 
type StoreSetters<T> = {
  [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void;
};
 
type StoreSubscriptions<T> = {
  [K in keyof T as `on${Capitalize<string & K>}Change`]: (
    callback: (newValue: T[K], oldValue: T[K]) => void
  ) => () => void;
};
 
type Store<T> = StoreGetters<T> & StoreSetters<T> & StoreSubscriptions<T>;
 
type AppStore = Store<State>;
// {
//   getCount: () => number;
//   getName: () => string;
//   getItems: () => string[];
//   setCount: (value: number) => void;
//   setName: (value: string) => void;
//   setItems: (value: string[]) => void;
//   onCountChange: (callback: ...) => () => void;
//   onNameChange: (callback: ...) => () => void;
//   onItemsChange: (callback: ...) => () => void;
// }
Tip

템플릿 리터럴 타입은 라이브러리 설계에서 특히 강력합니다. 사용자가 문자열 기반 API(라우트 경로, CSS 프로퍼티, 이벤트 이름 등)를 사용할 때, 타입 수준에서 패턴을 검증하고 자동완성을 제공하여 개발 경험을 크게 향상시킵니다.

정리

템플릿 리터럴 타입은 타입 수준에서 문자열을 조작하고 패턴을 매칭하는 강력한 도구입니다. 유니온 타입과의 조합으로 모든 가능한 문자열 변형을 자동 생성하고, infer를 활용한 패턴 매칭으로 문자열 구조를 분해하며, 매핑 타입과 결합하여 기존 타입에서 새로운 API 타입을 자동으로 도출할 수 있습니다.

다음 장에서는 5장에서 간략히 다뤘던 infer 키워드를 더 깊이 파고들어, 고급 타입 추론 패턴과 TypeScript 5.x에서 추가된 infer extends 구문을 집중적으로 다룹니다.

이 글이 도움이 되셨나요?

관련 주제 더 보기

#typescript#performance#devtools

관련 글

프로그래밍

8장: infer 키워드와 타입 추론 마스터

TypeScript infer 키워드의 고급 활용 패턴, infer extends 구문, 공변/반변 위치에서의 추론, 그리고 실전 타입 추출 패턴을 심층 분석합니다.

2026년 2월 5일·14분
프로그래밍

6장: 매핑 타입과 키 재매핑 고급 패턴

TypeScript 매핑 타입의 원리, 수정자 조작, 키 재매핑(as 절), 그리고 조건부 타입과의 결합 패턴을 실전 예제와 함께 다룹니다.

2026년 2월 1일·12분
프로그래밍

9장: NoInfer와 새로운 유틸리티 타입

TypeScript 5.4의 NoInfer 유틸리티 타입과 5.x에서 추가된 새로운 타입 도구들을 활용한 라이브러리 설계 패턴을 다룹니다.

2026년 2월 7일·14분
이전 글6장: 매핑 타입과 키 재매핑 고급 패턴
다음 글8장: infer 키워드와 타입 추론 마스터

댓글

목차

약 12분 남음
  • 템플릿 리터럴 타입의 기본
  • 내장 문자열 조작 유틸리티
  • 패턴 매칭과 infer
    • 문자열 변환 유틸리티
    • 문자열 치환
  • 실전 패턴
    • 타입 안전 CSS 프로퍼티
    • 타입 안전 라우트 파라미터
    • 타입 안전 SQL 쿼리 빌더
    • 점 표기법 경로 타입
    • 타입 안전 이벤트 이름
  • 매핑 타입과의 결합 패턴 종합
  • 정리