Skip to main content

Command Palette

Search for a command to run...

TypeScript 배우기 - 13. 템플릿 리터럴 타입

Published
4 min read

핸드북 타입 조작 섹션의 마지막인 템플릿 리터럴 타입입니다. 템플릿 리터럴 타입은 문자열 리터럴 타입을 기반으로 유니온 타입을 통해 확장됩니다.

다음의 코드를 보시죠.

type World = "world";

// Greeting 타입은 "hello world" 문자열 리터럴 타입
type Greeting = `hello ${World}`;

보간된 위치에서 유니온 타입을 사용할 수 있습니다.

type EmailLocaleIDs = "welcome_email" | "email_heading";
type FooterLocaleIDs = "footer_title" | "footer_sendoff";

// 유니온 결합에 의해 `"welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"`으로 확장
type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;

다음과 같이 두 개의 유니온 타입이 교차 곱해집니다.

type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`;
type Lang = "en" | "ja" | "pt";

// "en_welcome_email_id" | "en_email_heading_id" | "en_footer_title_id" | "en_footer_sendoff_id" | "ja_welcome_email_id" | "ja_email_heading_id" | "ja_footer_title_id" | "ja_footer_sendoff_id" | "pt_welcome_email_id" | "pt_email_heading_id" | "pt_footer_title_id" | "pt_footer_sendoff_id"
type LocaleMessageIDs = `${Lang}_${AllLocaleIDs}`;

일반적으로 큰 문자열 합집합의 경우 사전을 사용해야 하지만 작은 문자열 합집합의 경우에 유용합니다.

타입 문자열 합집합

템플릿 리터럴 타입은 타입 내부의 정보를 이용해서 새로운 문자열을 정의할 때 유용합니다.

다음의 객체 타입의 속성이 변경될 때 특정한 동작을 하기를 원한다고 합시다.

const passedObject = {
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26,
};

이 때 필요한 것은 변경 이벤트 이름과 이 이벤트 이름으로 콜백 함수를 등록한 두개의 매개변수가 필요합니다. 여기서 변경 이벤트 이름은 firstNameChanged, lastNameChanged, ageChanged가 되게 하고 싶습니다.

이를 다음처럼 표현할 수 있습니다.

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26,
});

// makeWatchedObject has added `on` to the anonymous Object

person.on("firstNameChanged", (newValue) => {
  console.log(`firstName was changed to ${newValue}!`);
});

makeWatchedObject() 함수에 의해 주어진 매개 변수의 객체에 추가로 속성명 + Changed의 이벤트가 생성되고 속성이 변경될 때 콜벡 함수가 실행되도록 하는 on() 함수가 추가됩니다.

type PropEventSource<Type> = {
    on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) => void): void;
};

/// Create a "watched object" with an 'on' method
/// so that you can watch for changes to properties.
declare function makeWatchedObject<Type>(obj: Type): Type & PropEventSource<Type>;

Type의 모든 속성에 대한 이벤트명으로 유니언 리터럴 타입으로 생성하는 코드를 볼 수 있습니다. 그리고 makeWatchedObject() 함수는 반환형으로 TypePropEventSource<Type> 타입을 결합합니다.

이제 Type의 속성명으로 생성된 이벤트명의 유니온 리터럴 타입에 의해 다음의 코드에서 올바르지 않은 이벤트명은 오류로 처리할 수 있게 되었습니다.

const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26
});

person.on("firstNameChanged", () => {});

// Prevent easy human error (using the key instead of the event name)
person.on("firstName", () => {});
>          ~~~~~~~~~
> Argument of type '"firstName"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.

// It's typo-resistant
person.on("frstNameChanged", () => {});
>          ~~~~~~~~~~~~~~~
> Argument of type '"frstNameChanged"' is not assignable to parameter of type '"firstNameChanged" | "lastNameChanged" | "ageChanged"'.

템플릿 리터럴을 사용한 추론

하지만 위의 예시는 매개변수의 타입에 any를 사용했으므로 완전하지 않습니다.

newValue의 타입이 속성의 타입이 되도록 다음과 같이 수정할 수 있습니다.

type PropEventSource<Type> = {
    on<Key extends string & keyof Type>
        (eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void ): void;
};
const person = makeWatchedObject({
  firstName: "Saoirse",
  lastName: "Ronan",
  age: 26
});

// (parameter) newName : string
person.on("firstNameChanged", newName => {
    console.log(`new name is ${newName.toUpperCase()}`);
});

// (parameter) newAge: number
person.on("ageChanged", newAge => {
    if (newAge < 0) {
        console.warn("warning! negative age");
    }
});

추론은 다양한 방식으로 결합될 수 있으며, 종종 문자열을 분해하고 다양한 방식으로 재구성할 수 있습니다.

고유 문자열 조작 타입

문자열 조작을 돕기 위해 TypeScript는 문자열 조작에 필요한 타입 집합이 포함되어 있습니다. 이러한 타입은 성능을 위해 컴파일러에 내장되어 있으며 .d.ts 파일로는 찾을 수 없습니다.

Uppercase<StringType>

대문자 문자열 리터럴 타입으로 변환합니다.

type Greeting = "Hello, world"
// type ShoutyGreeting = "HELLO, WORLD"
type ShoutyGreeting = Uppercase<Greeting>

type ASCIICacheKey<Str extends string> = `ID-${Uppercase<Str>}`
// type MainID = "ID-MY_APP"
type MainID = ASCIICacheKey<"my_app">

Lowercase<StringType>

소문자 문자열 리터럴 타입으로 변환합니다.

type Greeting = "Hello, world"
// type QuietGreeting = "hello, world"
type QuietGreeting = Lowercase<Greeting>

type ASCIICacheKey<Str extends string> = `id-${Lowercase<Str>}`
// type MainID = "id-my_app"
type MainID = ASCIICacheKey<"MY_APP">

Capitalize<StringType>

문자열 리터럴 타입의 첫번째 문자를 대문자로 변환합니다.

type LowercaseGreeting = "hello, world";
// type Greeting = "Hello, world"
type Greeting = Capitalize<LowercaseGreeting>;

Uncapitalize<StringType>

문자열 리터럴 타입의 첫번째 문자를 소문자로 변환합니다.

type UppercaseGreeting = "HELLO WORLD";
// type UncomfortableGreeting = "hELLO WORLD"
type UncomfortableGreeting = Uncapitalize<UppercaseGreeting>;
21 views

More from this blog

개발, 테스트, 운영에서의 도커 활용

핵심 원칙: "한 번 빌드하고, 어디서든 실행한다 (Build once, run anywhere)" 도커의 가장 큰 장점은 환경 일관성입니다. 동일한 도커 이미지를 사용하여 개발, 테스트, 운영 환경을 구성함으로써 "제 PC에서는 됐는데..." 하는 문제를 최소화할 수 있습니다. 1. 개발 단계 (Development) 목표: 빠른 코드 변경 반영, 쉬운 디버깅, 실제 운영 환경과 유사한 환경 구성. Docker 사용 방안: Dockerf...

May 9, 20256 min read15

[EF Core] 데이터 삭제 시 소프트 삭제 적용

DB에서 데이터를 삭제하면 일반적으로 복구할 수 없습니다. 또한 관계에 따라 영구 삭제 자체가 어려울 수도 있습니다. 그래서 데이터를 영구 삭제하는 대신 IsDeleted 속성을 true로 주고 IsDeleted 속성을 필터링해서 조회하는 방법을 사용하기도 합니다. 이를 소프트 삭제라고 합니다. 그런데 EF에서 알아서 데이터 삭제 시 소프트 삭제를 하고 쿼리시 IsDeleted 속성을 체크해서 삭제한 데이터를 제외한 데이터만 쿼리하게 하는 ...

Mar 18, 20243 min read19

[EF Core] ValueConverter를 이용해서 엔터티 속성의 도메인 관리

EF Core를 사용하면서 문자열 길이 등의 특성을 일일이 지정하는 것은 번거롭습니다. ... [MaxLength(32)] public string? 제목 { get; set; } 엔터티가 한 개일 때는 상관이 없으나 제목 유형이 여러 엔터티에 사용될 경우 유형을 지정하기 번거롭습니다. 속성 유형을 도메인으로 관리하면 참 편할텐데요, ValueConverter를 이용할 수 있습니다. 그런데 이것을 인터페이스 정적 추상를 사용해서 다음처럼 ...

Mar 16, 20242 min read8

디모이 블로그

154 posts

.NET 관련 기술을 선호하고 새로운 언어를 배우는데 관심이 있습니다.