코딩/Typescript

타입스크립트 Utility Types - 타입스크립트 TypeScript 강좌 9편

드리프트 2021. 12. 12. 15:07
728x170

 

 

 

 

안녕하세요?

 

이번 시간에는 타입스크립트에서 자체적으로 제공하는 Utility Types에 대해 알아 보겠습니다.

 

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

 

Documentation - Utility Types

Types which are globally included in TypeScript

www.typescriptlang.org

 

 

공식 홈페이지를 보면 Utility Types으로 제공하는게 아주 많은 데요.

 

우리는 가장 많이 쓰이는 몇가지에 대해 예를 들어 알아 보겠습니다.

 

 

1. Partial<Type>

 

먼저 Partial<Type>에 대해 알아 보겠습니다.

 

Partial<Type>을 언제 쓰이는지는 예를 들어 보면 쉽게 이해 할 수 있습니다.

 

다음과 같이 MyUser라는 인터페이스가 있다고 합시다.

 

interface MyUser {
    name: string;
    id: string;
    email?: string;
}

 

name과 id는 옵셔널이 아니고 email만 옵셔널로 지정되어 있네요.

 

이와 같은 MyUser 인터페이스를 이용해서 MyUser를 편집할려고 할때가 있습니다.

 

예를 들어 email 부분을 수정할려고 한다면 어떻게 해야 할까요?

 

const merge = (user: MyUser, overrides: MyUserOptionals): MyUser => {
    return {
        ...user,
        ...overrides
    }
}

 

위와 같은 merge 함수를 보시면 기존 user 데이터와 overrides 데이터를 합치는 함수인데요.

 

overrides 데이터는 타입이 MyUserOptionals 입니다.

 

interface MyUserOptionals {
    name?: string;
    id?: string;
    email?: string;
}

 

MyUserOptionals 인터페이스를 보면 모든 항목이 옵셔널로 되어 있습니다.

 

즉, 어떤 항목이든지 변경하려는 항목만 넣을 수 있다는 뜻입니다.

 

이제 merge 함수를 사용해 볼까요?

 

아래 코드를 보십시요.

console.log(merge({
    name: "Ki-hun",
    id: "456",
    email: "kihun@squid.com"
}, {
    email: "jungkihun@squid.com"
}))

 

첫번째 객체에다가 두번째 객체를 덮어 씌라는 얘기입니다.

 

그래서 MyUserOptions 인터페이스가 필요합니다.

 

실행 결과를 볼까요?

 

 

예상 되로 잘 실행 됐습니다.

 

Partial<Type> 을 설명할 필요없이 별다른 수고없이 실행도 잘되고 코드 이해도 쉬었습니다.

 

그런데 만약 MyUser가 phone 항목이 추가로 생겼다면 어떻게 할까요?

 

interface MyUser {
    name: string;
    id: string;
    email?: string;
    phone?: string;
}

 

그러면 MyUserOptionals 인터페이스도 바꿔줘야 합니다.

 

interface MyUserOptionals {
    name?: string;
    id?: string;
    email?: string;
    phone?: string;
}

 

뭔가 수작업이 계속 일어나는데요. 코딩할때 수작업은 힘만 들이는 꼴이고 나중에 코드 유지 보수가 안됩니다.

 

이럴 때 필요한데 Partial<Type>입니다.

 

Partial<Type>은 Type을 모두 옵셔널로 만들어 줄 수 있는데요.

 

이제 우리코드에서 MyUserOptionals 을 지워도 됩니다.

 

type MyUserOptionals = Partial<MyUser>

 

위와 같이 작성하시면 됩니다.

 

 

VS Code의 인텔리센스가 Partial<Type>에 의해 생성된 MyUserOptionals 의 상세 설명을 보여 주고 있는데요.

 

Partial<Type>의 역할 그대로 모든 항목을 옵셔널로 만들어 주고 있네요.

 

이제 실행해 볼까요?

 

 

아까와 같이 별다른 에러 없이 잘 실행 되고 있습니다.

 

즉, Partial<Type>은 위와 같은 경우 사용하면 아주 좋은 Utility Type입니다.

 

 

2. Required<Type>

 

 

Required<Type> 은 타입의 옵셔널을 없애주는 기능을 합니다.

 

interface MyUser {
    name: string;
    id: string;
    email?: string;
    phone?: string;
}

 

MyUser에서 옵셔널 항목은 email과 phone 항목이 있습니다.

 

그런데 다음과 같이 하면 어떻게 될까요?

 

type RequiredMyUser = Required<MyUser>

 

바로 아래 그림처럼 됩니다.

 

email과 phone의 옵셔널 부분을 없애줬네요.

 

Requried<Type>은 이런 기능을 합니다.

 

 

3. Pick<Type, Keys>

 

세번째로 알아볼 유틸리티 타입은 Pick인데요.

 

간단히 예를 들어 설명하겠습니다.

 

위에서 MyUser 항목이 있는데요.

 

여기서 email과 name 부분을 발췌하는 항목을 만들고 싶을 때가 있습니다.

 

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

 

type JustEmailAndName = Pick<MyUser, "email" | "name">

 

 

인텔리센스는 정말 똑똑하네요. Pick 할 때 항목을 넣을 수 있는 Keys를 친절하게 알려 주고 있네요.

 

 

우리의 새로운 타입인 JustEmailAndName은 email이 옵셔널이고 name 항목이 있는 새로운 타입이 되었네요.

 

 

 

4. Record<Keys, Types>

 

이번에 알아볼 유틸리티 타입은 좀 어려운 타입인데요.

 

바로 Record 타입입니다.

 

예를 들어 알아 볼까요?

 

const mapById = (users: MyUser[]): Record<string, MyUser> => {
    return users.reduce((a, v) => {
        return {
            ...a,
            [v.id]: v,
        }
    }, {})
}

console.log(mapById([
    { id: "foo", name: "Mr, Foo" },
    { id: "baz", name: "Mr. Baz" }
]));

 

mapById 함수는 MyUser[] 배열을 보여주는 함수인데요.

 

이 함수의 리턴타입이 바로 Record입니다.

 

Record타입은 이렇듯 특정 타입과 키를 이용해서 레코드를 만들 때 편한데요.

 

실행 결과를 볼까요?

 

 

실행 결과를 보시면 mapById 함수에 의해 id에 따라 그 id에 해당되는 객체를 전부 보여 주고 있습니다.

 

 

5. Omit<Type, Keys>

 

위의 예에서 보면 id가 중복되었는데요.

 

이럴 때 쓸 수 있는 유틸리티 타입이 바로 Omit입니다.

 

위에서 알아 보았던 Pick의 정반대 역할을 합니다.

 

const mapById = (users: MyUser[]): Record<string, Omit<MyUser, "id">> => {
    return users.reduce((a, v) => {
        return {
            ...a,
            [v.id]: v,
        }
    }, {})
}

실행결과를 볼까요?

 

별다르게 변한게 없습니다.

 

Omit은 타입을 지정해주는 역할을 하기 때문에 id 를 빼고 싶으면 코드에서 직접 빼야 합니다.

 

다음과 같이 하면 됩니다.

 

const mapById = (users: MyUser[]): Record<string, Omit<MyUser, "id">> => {

    return users.reduce((a, v) => {
        const { id, ...other } = v;
        return {
            ...a,
            [id]: other
        }
    }, {})
}

 

위와 같이 코드를 바꾸고 실행해 볼까요?

 

실행 결과를 보시면 id 항목이 빠졌습니다.

 

Record<string, Omit<MyUser, "id">> 라고 쓰여져 있어 너무 복잡한데요.

 

Omit 부분을 다른 타입으로 만들어 봅시다.

 

type UserWithoutID = Omit<MyUser, "id">;

const mapById = (users: MyUser[]): Record<string, UserWithoutID> => {

    return users.reduce((a, v) => {
        const { id, ...other } = v;
        return {
            ...a,
            [id]: other
        }
    }, {})
}

 

코드가 훨씬 보기 좋아졌죠?

 

 

마지막으로 보너스로 알아볼 기능이 있는데요.

 

만약 MyUser 타입에서 id 부분이 string이 아닌 number라면 어떻게 될까요?

 

그러면 Record<string,.........> 부분을 수작업으로 Record<number,.....> 처럼 매번 바꿔줘야 합니다.

 

이런 수고를 덜어줄 수 있는 방법이 있는데요.

 

const mapById = (users: MyUser[]): Record<MyUser["id"], UserWithoutID> => {

    return users.reduce((a, v) => {
        const { id, ...other } = v;
        return {
            ...a,
            [id]: other
        }
    }, {})
}

 

Record<MyUser["id"], ......> 라고 쓰면 됩니다.

 

이렇게 쓰시면 MyUser의 "id" 타입이 지정되게 됩니다.

 

이상 타입스크립트의 유틸리티 타입에 대해 알아 보았습니다.

 

그리드형