본문 바로가기
타입스크립트(TS)

[TS] 커스텀 유틸리티 타입 활용하기

by goodchuck 2024. 7. 28.

목차

     

     

     커스텀 유틸리티 타입?

    타입스크립트로 프로젝트를 진행하다 보면 표현하기 힘든 타입을 마주할 때가 있다. 원하는 타입을 정확하게 설정해야만 해당 컴포넌트, 함수의 안전성과 사용성을 높일 수 있지만 타입스크립트에서 제공하는 유틸리티 타입만으로는 표현하는데 한계가 있다.

    이럴때

    유틸리티 타입을 활용한 커스텀 유틸리티 타입을 제작해서 사용하면 된다.

     

     커스텀 유틸리티 타입 종류

    유틸리티 함수를 활용해 styled-components의 중복 타입 선언 피하기

    리액트 컴포넌트를 구현할 때 여러 옵션을 props로 받아 유연한 컴포넌트로 만들 수 있다.

    ex) background-color, size 등등

    이와 같은 props는 styled-components에 전달되며 styled-components에도 해당 타입을 정확하게 작성해줘야 한다.

    styled-compnents로 만든 컴포넌트에 넘겨주는 타입은 props에서 받은 타입과 동일할 때가 대부분이다.

    이런 경우에는 타입스크립트에서 제공하는 Pick, Omit 같은 유틸리티 타입을 잘 활용하여 코드를 간결하게 작성할 수 있다.

     

     기존의 직접 선언한 props 타입

    type Props {
    	height?: string | undefined;
        color? : "MINT_2AC1BC" | "MINT_269C98" | "RED_F45452"| ... more | undefined;
        isFull? : boolean | undefined;
        className? : string | undefined;
    }

     

     유틸리티 타입 Pick를 사용하여 선언한 StyledProps 타입

    type StyledProps = Pick<Props, 'height' | 'color' | 'isFull'>

    위처럼 Pick 유틸리티 타입을 활용해서 props에서 필요한 부분만 선택하여 styled-components 컴포넌트 타입을 정의하면, 중복된 코드를 작성하지 않아도 되고 유지보수를 더욱 편리하게 할 수 있게 된다.

    이외에도 상속받는 컴포넌트 혹은 부모 컴포넌트에서 자식 컴포넌트로 넘겨주는 props 등에도 Pick, Omit 같은 유틸리티 타입을 활용할 수 있다.

     

    PickOne 유틸리티 함수

    타입스크립트에는 서로 다른 2개 이상의 객체를 유니온 타입으로 받을 때 타입 검사가 제대로 진행되지 않는 이슈가 있다.이런 문제를 해결하기 위해 PickOne이라는 이름의 유틸리티 함수를 사용한다.

     

    시나리오) Card, Account 중 하나의 객체만 받고 싶은 상황에서 Card | Account 로 타입을 작성하면 의도한 대로 타입 검사가 이루어지지 않는다. 또한 함수의 인자로 {card:'hundai', account:'hana'}중 하나만 받고 싶어도 실제로는 두개의 속성을 모두 받아도 타입 에러가 발생하지 않는다.

     

     타입 에러가 발생하지 않는 이유?

    유니온은 합집합이기 때문에 하나씩만 할당한 것도 속성이 모두 포함되는것도 합집함의 범주에 들어가기 때문에 타입 에러가 발생하지 않는다.

     

    이런 문제를 해결하기 위해 타입스크립트에서는 식별할 수 있는 유니온 기법을 자주 활용한다.

     

     커스텀 유틸리티 없이 해결하는 방법

    식별할 수 있는 유니온이라는 각 타입에 type이라는 공통된 속성을 추가하여 구분짓는 방법이 있다.

    type Card = {
    	type: "card";
    	card: string;
    };
    
    type Account = {
    	type: "account";
    	account: string;
    };

    식별할 수 있는 유니온으로 문제를 해결할 수 있다.

     

    단점으로는

    • 일일이 type을 다 넣어줘야 하는 불편함이 생긴다.
    • 이미 구현된 상태에서 식별할 수 있는 유니온을 적용하려면 해당 함수를 사용하는 부분을 모두 수정해야한다.
    • 실제로 수정하지 않은 부분이 생긴다면 또 다른 문제가 발생할 수 있다.

     PickOne 커스텀 유틸리티 타입 구현하기

    type PickOne<T> = {
    	[P in keyof T]: Record<P, T[P]> & Partial<Record<Exclude<keyof T,P>, undefined>>;
    }[keyof T];

     

    NonNullable 타입 검사 함수를 사용하여 간편하게 타입 가드하기

    타입 가드는 타입스크립트에서 많이 사용한다.

    특히 null을 가질 수 있는 값의 null처리는 자주 사용되는 타입 가드 패턴의 하나이다.

     

    일반적으로 if문을 사용해서 null 처리 타입 가드를 적용하지만, is 키워드와 NonNullable타입으로 타입 검사를 위한 유틸 함수를 만들어서 사용할 수 도 있다.

     

     NonNullable 타입이란

    타입스크립트에서 제공하는 유틸리티 타입으로 제네릭으로 받는 T가 null 또는 undefined 일 때 never 또는 T를 반환하는 타입.

    NonNullable을 사용하면 null이나 undefined가 아닌 경우를 제외할 수 있다.

    type NonNullable<T> = T extends null | undefined ? nver : T;

     

     

     null, undefined를 검사해주는 NonNullable 함수

    NonNullable 유틸리티 타입을 사용하여 null 또는 undefined를 검사해주는 타입 가드 함수를 만들어 쓸 수 있다.

     

    NonNullable 함수는 매개변수인 value가 null 또는 undefined라면 false를 반환.

    is키워드가 쓰였기 때문에 NonNullable 함수를 사용하는 쪽에서 true가 반환된다면 넘겨준인자는 null이나 undefined가 아닌 타입으로 타입 가드(타입이 좁혀진다)가 된다.

     

    function NonNullable<T>(value: T): value is NonNullable<T> {
    	return value !== null && value !== undefined;
    }

     

     

     Promise.all을 사용할 때 NonNullable 적용하기

    시나리오) 각 상품 광고를 노출하는 API 함수 레이어

    상품 광고 API는 상품 번호인 shopNumber 매개변수에 따라 각기 다른 응답 값을 반환하는 광고 조회 API

    여러 상품의 광고를 조회할 때 하나의 API에서 에러가 발생한다고 해서 전체 광고가 보이지 않으면 안되기 때문에

    try-catch문을 사용하여 에러가 발생할 때는 null을 반환하는 함수가 있다.

     

    class AdCampaignAPI {
    	static async operating(shopNo: number): Promise<AdCampaign[]>{
        	try {
            	return await fetch(`/ad/shopNumber=${shopNo}`);
            }
            catch (error) {
            	return null;
            }
        }
    }

     

    const shopList = [
    	{ shopNo: 100, category: "chicken"},
        { shopNo: 101, category: "pizza" },
        { shopNo: 102, category: "noodle" }
    ]
    
    const shopAdCampaignList = await Promise.all(shopList.map(
    	(shop) => AdCampaignAPI.operating(shop.shopNo))
    );
    
    const shopAds = shopAdCampaignList.filter(NonNullable);

     

    shopAdCampaignList는 AdCampaignAPI.operating 함수에서 null을 반환할 수 있기 때문에 shopAdCampaignList 타입은 Array<AdCampaign[] | null> 로 추론됨.

     

    NonNullable 함수를 사용하지 않고 단순하게 필터링한다면 [shopAdCampaignList.filter((shop)=> !! shop)]가 원하는 Array<AdCampaign[]> 타입으로 추론되는 것이 아닌, null이 될 수 있는 상태인 Array<AdCampaign[] | null>로 추론됨

     

    마지막의 NonNullable함수를 통해 shopAds는 원하는 타입인 Array<AdCampaign[]>로 추론이 가능해진다.

    '타입스크립트(TS)' 카테고리의 다른 글

    [TS]@types  (1) 2024.11.02
    엠비언트 타입(Ambient Type)(.d.ts)  (0) 2024.11.02
    [TS] index.d.ts 알아보기  (0) 2024.06.16