코딩/Typescript

Mapped Type - 타입스크립트 강좌 14편

드리프트 2022. 3. 1. 21:59
728x170

 

안녕하세요?

 

오늘은 좀 어려운 주제인데요.

 

먼저, RPG 게임에서 player의 특성을 정의하는 타입을 만들려고 한다고 가정해 봅시다.

 

어떻게 해야 할까요?

 

먼저, name이 있어야겠죠?

 

type MyFlexibleInfo = {
    name: string;
}

그다음으로 race (종족)이나 age (나이)가 있어야겠죠?

 

만약, MyFlexibleInfo 가 두 가지의 타입만 있다고 하고 race나 age 중에 한 개만 자유롭게 넣게 하고 싶을 때는 어떻게 할까요?

 

바로 타입 머지 (type merge) 하면 되는데요.

 

type MyFlexibleInfo = {
    name: string;
} & Record<string, string>

 

Record 유틸리티 타입으로 key : value 타입을 추가할 수 있게 했습니다.

 

type MyFlexibleInfo = {
    name: string;
} & Record<string, string>

const player1: MyFlexibleInfo = {
    name: "TS",
    race: "human"
}

console.log(player1);

위 코드처럼 race 부분을 넣을 수 있고,

 

type MyFlexibleInfo = {
    name: string;
} & Record<string, string>

const player1: MyFlexibleInfo = {
    name: "TS",
    age: "14"
}

console.log(player1);

위 코드처럼 age 부분을 string화 해서 넣을 수 도 있습니다.

 

그러면 race나 age 부분을 자유롭게 추가하려면 어떻게 해야 할까요?

 

바로 다음과 같이 하시면 됩니다.

 

type MyFlexibleInfo = {
    name: string;
    [key: string] : string | number;
}

key value 부분을 위와 같이 지정하면 key 부분은 string이 되고 value 부분은 string이나 number가 됩니다.

 

type MyFlexibleInfo = {
    name: string;
    [key: string]: string | number;
}
const player1: MyFlexibleInfo = {
    name: "TS",
    race: "human",
    age: 14,
}

console.log(player1);

어떤가요?

 

타입 정의에 있어 매우 유용하고 유연한 타입이 되지 않았나요?

 

 

Re-Mapped Type

 

타입을 리맵(바꿀)할 수 있는 Type을 알아볼까 하는데요.

 

타입 스크립트 다큐먼트에서 예제를 가져와서 적용해 보겠습니다.

 

https://www.typescriptlang.org/docs/handbook/2/mapped-types.html

 

Documentation - Mapped Types

Generating types by re-using an existing type.

www.typescriptlang.org

 

type OptionsFlags<T> = {
  [Property in keyof T]: boolean;
};

Property를 boolean으로 바꾸는 플래그인데요.

 

Property는 T 타입의 keyof 연산자를 이용해서 가져왔습니다.

 

이제 실제 코드에서 적용해 볼까요?

 

interface PlayerInfo {
    name: string;
    age: number;
}

 

PlayerInfo 가 위와 같이 interface로 있다고 합시다.

 

name은 string이고, age는 number인데요.

 

name과 age 타입을 바꿔볼까 합니다. Re-Mapping이라고 하는데요.

 

interface PlayerInfo {
    name: string;
    age: number;
}

type OptionsFlags<T> = {
    [Property in keyof T]: boolean;
}

type PlayerInfoOption = OptionsFlags<PlayerInfo>;

이제 PlayerInfoOption의 타입을 살펴볼까요?

 

위 스크린숏을 보시면 PlayerInfoOption의 name과 age 타입이 boolean으로 바뀌었습니다.

 

신기하네요.

 

이번에는 string으로 바꿔 볼까요?

 

 

코딩할 때 이게 필요할 때가 있을 때가 언젠가는 한번 나오겠죠.

 

좀 더 나아가서 콜백 함수 타입도 바꿔 볼까요?

 

function listenToObject(obj, listeners): void {
    console.log("Wait");
}

const player2: PlayerInfo = {
    name: "GOOD",
    age: 14
}

listenToObject(player2, {
    onNameChange: (v: string) => { },
    onAgeChange: (v: number) => { },
})

 

listenToObject 함수가 있고, listeners는 onNameChange과 onAgeChange 함수가 있다고 합시다.

 

이제 제네릭을 적용해 볼까요?

 

type Listeners<T> = {
    [Property in keyof T]: () => void;
}

function listenToObject<T>(obj: T, listeners: Listeners<T>): void {
    console.log("Wait");
}

const player2: PlayerInfo = {
    name: "GOOD",
    age: 14
}

Listeners <T> 도 만들었습니다.

 

마지막으로 PlayerInfoListeners 도 타입을 아래와 같이 지정해 줬습니다.

 

type PlayerInfoListeners = Listeners<PlayerInfo>;

 

이 PlayerInfoListeners 타입을 볼까요?

 

 

어떤가요? 콜백 함수의 타입도 () => void로 바꿨습니다.

 

타입 Listeners <T>에서 리턴되는 콜백 함수의 인자를 아래와 같이 바꿔 볼까요?

type Listeners<T> = {
    [Property in keyof T]: (newValue: T[Property]) => void;
}

 

이렇게 되면 PlayerInfoListeners는 어떤 타입이 되어 있을까요?

 

와우, 콜백 함수의 인수를 바꿔 버렸네요.

 

void도 다르게 바꿔볼까요?

 

type Listeners<T> = {
    [Property in keyof T]: (newValue: T[Property]) => string;
}

 

 

어떤가요?

 

Re-Mapped Type 방법인데요.

 

외울 거는 [Property in keyof T]를 꼭 기억하시길 바랍니다.

그리드형