Advanced Typescript

PEPPERMINT100·2020년 12월 20일
0

서론

타입스크립트를 간단하게만 사용하더라도 코드 작성에서 꽤 많은 이점을 가져올 수 있다. 하지만 타입스크립트에는 다른 정적 타이핑을 가지는 프로그래밍 언어보다 복잡한 타이핑이 가능하도록 되어있다. 예를 들면 lookup type, mapped type, keyof 등이 있는데, 대충 보면 무슨 소리인지 하나도 모르겠지만 알고 나면 코드 길이를 꽤 많이 줄일 수 있다.

하지만 사용 방법이 생각보다 복잡하고 나 역시 뭔지는 정확히 모른채로 대충 이런식으로 동작하는 것을 알아서 사용해 왔던 타입스크립트의 중급 문법을 정확히 알기 위해 다뤄보고자 한다. 이 내용에 대해서 시리즈를 여러 개 작성하는 것은 의미가 없어 보이므로 이 글에 조금씩 쓰고 이 후에 배우게 되는 내용은 더 추가하는 식으로 작성해보도록 하겠다.

이 글에서 다루지 않는 부분

  • 타입스크립트 기본 문법
  • 제네릭

다른 프로그래밍 언어의 타이핑과 중첩되는 부분은 최대한 없애고 제가 몰랐던 부분과타입스크립트만의 특징을 가지는 중급 문법에 대해서만 다루겠습니다.

keyof

keyof는 어떤 타입(이하 타입스크립트의 interface와 혼용할 수 있습니다.) 의 키 부분들만을 모아 놓은 것을 뜻한다. 예를 들면

interface Person {
    name: string;
    age: number;
    job: string;
}

Person 이라는 타입이 있다고 하자. 3개의 키 name, age, job을 갖게 된다. 만약 우리가 이 사람의 정보를 보는 코드를 작성한다고 하자.

function watchPersonInfo(person: Person, key: string){
    console.log(person[key]);
}

const pepper: Person = { name: "pepper", age:26.9, job: "developer" }

watchPersonInfo(pepper, 'name');\
/// output : pepper

위와 같이 작성했다. 전혀 문제 없이 name키의 값인 pepper가 정상적으로 출력된다. 하지만 여기서

watchPersonInfo(pepper, 'address');

이렇게 없는 정보를 보여주려고 한다면 어떻게 될까? watchPersonInfo 함수의 파라미터인 keystring으로 타이핑이 되어있기 때문에 그저 string이면 타입스크립트가 오류를 발견해내지 못한다. 우리는 key 파라미터에 들어갈 인자가 Person의 키이길 바란다. 영어로는 keyof Person이 될것만 같다.

function watchPersonInfo(person: Person, key: keyof Person){
    console.log(person[key]);
}

const pepper: Person = { name: "pepper", age:26.9, job: "developer" }

watchPersonInfo(pepper, 'address'); // 'address'에 error

그렇게 key의 타입을 keyof Person으로 바꿔주었더니 바로 'address'에 오류가 생긴다. 여기서 keyof Person은 즉

'name' | 'age' | 'job'

과 같은 유니온 타입의 역할을 하게 되는 것이다.

Mapped Types

위에서 사용한 pepper라는 사람의 정보를 읽을 수만 있고 바꿀 수는 없도록 코드를 작성한다고 하자. 즉

pepper.age = 35; // 여기서 에러를 발생시키고 싶다고 하자.

이런식으로 변경이 불가능하도록 해야한다. 이런 경우엔 readonly라는 방법을 사용하면 된다. 즉 Person

interface Person {
    readonly name: string;
    readonly age: number;
    readonly job: string;
}

이렇게 바꿔주면 우리가 원하던 대로 에러가 난다. 하지만 타입에 키가 수 없이 많다면 그 만큼 readonly를 직접 타이핑해주어야 한다. 그리고 어느날 우리는 readonly가 있는 타입과 없는 타입이 모두 필요하다고 할 때 두 개의 타입을 전부 직접 타이핑에서 만들어주게 될 것이다. 물론 복사붙여넣기를 할테지만 그만큼 코드의 길이가 길어진다.

이런 경우에 Mapped Types라는 특징을 이용할 수 있는데, Mapped Types는 아래와 같은 형태로 이루어진다.

type ReadonlyPerson = {
    [loop]: output;
}

type 키워드를 사용해주어야 한다. 그렇지 않으면 Array 타입을 갖는 요소가 된다.

여기서 이제 loopoutput을 채워보자면

type ReadonlyPerson = {
    [K in 'name' | 'age' | 'job']: Person[K];
}

이렇게 된다. 이러면 Person 내에 모든 name, age, job 요소에 대한 키-밸류 타입 쌍들에 전부 적용시킬 수 있는 Mapped Types과 완성된다. 위에서 배운 keyof를 활용해서 조금 더 간단하게 해보자면

type ReadonlyPerson = {
    [K in keyof Person]: Person[K];
}

그리고 모든 요소에 readonly를 주고 싶으므로

type ReadonlyPerson = {
    readonly [K in keyof Person]: Person[K];
}

이렇게 앞에 readonly를 붙여주기만 하면 된다. 여기서 더 간단하고 제네릭하게 가자면

type Readonly<T> = {
    readonly [K in keyof T]: T[K];
}

이렇게 Person 까지 T로 대체할 수 있다. 그리고 심지어 더 간단하게 가자면

const pepper: Readonly<Person> = { name: "pepper", age:26.9, job: "developer" };

지금까지 작성한 타입을 지워버리고 그냥 타입스크립트에 기본적으로 내장되어 있는 Readonly를 쓰면 된다. 사실 Readonly는 타입스크립트에 내장된 Utility Types 중 하나이지만 가장 대표적인 Mapped Types를 사용하는 방법이기에 직접 구현해 보았다.

...

profile
기억하기 위해 혹은 잊어버리기 위해 글을 씁니다.

0개의 댓글