Menu

TypeScript 템플릿 리터럴 타입: 문법과 예제

템플릿 리터럴 타입은 JavaScript 템플릿 문자열과 같은 백틱 문법으로 문자열 리터럴 타입을 만듭니다: on${Capitalize<E>}. 문법, 유니언이 곱해지는 방식, Uppercase와 Capitalize, ${number}px 같은 패턴, 매핑된 타입으로 만드는 getter, infer로 문자열 파싱하기를 알아봅니다.

이 페이지에는 실행 가능한 에디터가 있습니다 - 편집하고 실행하면 결과를 바로 볼 수 있습니다.

템플릿 리터럴 타입은 JavaScript 템플릿 문자열과 같은 백틱 문법으로 문자열 리터럴 타입을 만듭니다. `on${Capitalize<"click" | "focus">}`는 컴파일러가 계산한 "onClick" | "onFocus" 타입입니다.

템플릿 리터럴 타입은 컴파일 시점에만 존재합니다. 코드를 쓰는 동안 문자열 리터럴과 타입이 지정된 값을 검사할 뿐, JavaScript 출력에는 아무것도 더하지 않습니다. 위의 핸들러 테이블은 Record를 써서 이름마다 함수를 하나씩 요구합니다.

문법

백틱 안에는 리터럴 텍스트와 ${...} 자리 표시자를 씁니다. 자리 표시자에는 값이 아니라 타입이 들어갑니다. 문자열, 숫자, bigint, 불리언 리터럴 타입이나 그 유니언, 또는 string, number, bigint, boolean, null, undefined 같은 넓은 타입 중 하나입니다.

string이나 number 같은 넓은 타입을 자리 표시자에 넣으면 패턴이 됩니다. 타입은 `hello ${string}`로 남고, 모양이 맞는 문자열은 모두 받아들입니다. boolean처럼 유한한 유니언을 넣으면 멤버별로 펼쳐집니다.

유니언은 곱해진다

유니언이 여러 개면 결과는 모든 조합입니다.

크기 세 가지와 톤 두 가지를 곱해 멤버가 여섯 개 나옵니다. 개수는 빠르게 늘어납니다. 각각 글자 열 개짜리 유니언을 담은 자리 표시자가 다섯 개면 멤버가 100,000개가 되고, TypeScript는 error TS2590: Expression produces a union type that is too complex to represent 오류로 거부합니다. 정확한 값이 모두 필요하지 않다면 ${string} 같은 넓은 자리 표시자를 쓰세요.

Uppercase, Lowercase, Capitalize, Uncapitalize

문자열 리터럴 타입의 대소문자를 바꾸는 내장 타입이 네 개 있습니다. 이들은 intrinsic 타입으로, TypeScript 코드로 작성된 것이 아니라 컴파일러 내부에 구현되어 있습니다.

타입입력결과
Uppercase<S>"hello world""HELLO WORLD"
Lowercase<S>"Content-Type""content-type"
Capitalize<S>"hello world""Hello world"
Uncapitalize<S>"UserName""userName"

바뀌는 것은 타입뿐입니다. 그에 맞는 런타임 문자열을 만들려면 여전히 toUpperCase()를 호출하거나 직접 잘라서 첫 글자를 대문자로 바꾸고, 결과가 정확한 타입이라고 TypeScript에 알려줘야 합니다.

toUpperCase()의 반환 타입이 그냥 string이므로 as가 필요합니다. 호출하는 쪽에서 보는 것은 함수 시그니처이므로 capitalize("report")는 리터럴 타입 "Report"를 갖습니다.

문자열 패턴: ${number}px 등

패턴 타입은 정해진 모양의 문자열을 모두 받습니다. CSS 값, 접두사가 정해진 id나 키에 유용합니다.

${number}는 JavaScript가 숫자로 읽는 문자열을 모두 받는데, 생각보다 느슨합니다. "-3px", "1e3px", "0x10px" 모두 타입 검사를 통과합니다. 이런 패턴은 완전한 검증이 아니라 리터럴의 오타를 막는 장치로 생각하세요.

매핑된 타입과 템플릿 리터럴

템플릿 리터럴 타입이 가장 쓸모 있는 곳은 매핑된 타입의 as 절입니다. 여기서 다른 속성 이름으로부터 새 속성 이름을 만들어 냅니다.

Capitalize는 숫자나 심볼을 받지 않으므로 string & K로 문자열 키만 남깁니다. 각 콜백은 자신이 감시하는 속성에서 매개변수 타입을 가져옵니다.

infer로 문자열 파싱하기

조건부 타입 안에서 템플릿 리터럴은 문자열을 매칭하고 infer로 그 일부를 잡아낼 수 있습니다. 다음은 라우트 패턴에서 매개변수 이름을 뽑아냅니다.

호출에서 postId를 빼면 컴파일러가 누락되었다고 알려줍니다.

템플릿 표현식은 string으로 넓어진다

일반 코드의 템플릿 문자열 표현식은 모든 부분이 리터럴 타입이어도 string 타입이 됩니다. 템플릿 리터럴 타입을 유지하려면 as const를 붙이세요.

as const가 없으면 loose를 `log:${Level}`에 대입할 때 실패합니다. string은 어떤 값이든 될 수 있기 때문입니다.

자주 묻는 질문

TypeScript에서 템플릿 리터럴 타입이란 무엇인가요?

백틱과 ${...} 자리 표시자로 쓰는 문자열 리터럴 타입으로, JavaScript 템플릿 문자열과 같지만 타입 수준에서 동작합니다. type Greeting = `hello ${string}`는 hello 로 시작하는 모든 문자열을 받고, `on${Capitalize<"click">}`는 리터럴 타입 "onClick"입니다.

템플릿 리터럴 타입에 유니언을 넣으면 어떻게 되나요?

멤버마다 템플릿이 펼쳐지고, 유니언이 여러 개면 모든 조합이 만들어집니다. `${"sm" | "lg"}-${"red" | "blue"}`는 "sm-red" | "sm-blue" | "lg-red" | "lg-blue"입니다. 조합이 너무 많으면 TS2590 오류가 납니다.

Uppercase, Lowercase, Capitalize, Uncapitalize는 무엇을 하나요?

문자열 리터럴 타입을 변환하는 내장 타입입니다. Uppercase<"id">는 "ID", Lowercase<"ID">는 "id", Capitalize<"name">은 "Name", Uncapitalize<"Name">은 "name"입니다. 바뀌는 것은 타입뿐이므로, 런타임 문자열을 바꾸려면 여전히 toUpperCase() 같은 메서드를 호출해야 합니다.

템플릿 리터럴 타입이 런타임에 문자열을 검증하나요?

아니요. 다른 모든 TypeScript 타입처럼 컴파일 후 지워지므로, 컴파일 시점에 문자열 리터럴과 타입이 지정된 값만 검사합니다. JSON이나 사용자 입력처럼 런타임에 들어오는 문자열은 직접 코드로 검사하기 전까지 그냥 string입니다.

템플릿 문자열이 리터럴 타입이 아니라 string으로 추론되는 이유는 무엇인가요?

`on${event}` 같은 템플릿 표현식은 변수에 대입될 때 string으로 넓어집니다. as const를 붙이거나(`on${event}` as const) 대상 타입을 명시하면 TypeScript가 "onclick" | "onfocus" 같은 템플릿 리터럴 타입을 유지합니다.

Coddy programming languages illustration

Coddy로 코딩 배우기

시작하기