TS) "is" 키워드로 "as" 대체하기

2ast·2024년 4월 11일
0

Typescript의 is와 as

typescript에는 특정 객체의 타입을 변경할 수 있는 키워드로 "as"와 "is"를 제공한다. 이 중 as는 꽤나 친숙한데, ts를 처음 접했을 때 흔히 "as any"를 남발하고는 하기 때문이다.
하지만 as 키워드는 함부로 사용할 경우 런타임 에러를 유발하고 디버깅도 어렵게 만들기 때문에 주의깊게 사용해야만 한다.

const a = getSomeValue() as string[];
console.log(a.length) // getSomeValue가 어떤 함수인지도 모르고, 나중에 어떻게 바뀔지 모르기 때문에 섣부르게 타입을 단언하는 경우 문제가 발생하기 쉬운 코드가 된다.

반면 is 키워드를 사용하면 이런 리스크를 줄일 수 있게 되는데, is는 어떻게 사용되는지 예시로 살펴보겠다.
우리가 구현하는 Card Component에 BigCard와 SmallCard 두 종류가 있다고 가정해보자. 이 두 Card 컴포넌트는 각각 BigInfo와 SamllInfo를 렌더링하는데 사용된다.

interface BaseInfo {
	...
}

interface BigInfo extends BaseInfo {
	bigPhoto: string;
}

interface SmallInfo extends BaseInfo {
	smallPhoto: string;
}

이제 이 데이터를 리스트에 렌더링한다고 가정해보자.

const infoArr: (BigInfo | SmallInfo)[] = [...];
                                      
return <List>
    {infoArr.map((item)=>{
    	if(item.bigPhoto){
        	return <BigCard card={item as BigInfo}/>
        }
        if(item.smallPhoto){
            return <SmallCard card={item as SmallInfo}/>
        }
        return null
    })}                                      
</List>

위와같이 bigPhoto와 smallPhoto의 존재 여부를 기준으로 조건문을 주고, 거기에 맞춰 컴포넌트를 렌더링하는데, item은 여전히 BigInfo | SmallInfo 타입을 갖고 있기 때문에 BigInfo를 받는 BigCard 컴포넌트의 prop으로 적절하지 않다고 나온다. 이 문제를 해결하기 위해서 "as"를 이용해 type casting을 하는걸 볼 수 있다. 굉장히 직관적이지 않고, 코드도 장황해졌다. 지금은 하나의 필드로 값이 구분되는 간단한 케이스지만 만약 조금더 세분화되어서 구분되어야하는 타입이라면 조건문도 꽤나 어지러워질 것 같다.
이럴 때 "is"를 적용해 볼 수 있다. is는 타입 가딩 함수를 통해 하위 스코프에서 타입을 지정해줄 수 있다.

const isBigInfo = (info:any): info is BigInfo => {
	return !!info.bigPhoto
}

const isSmallInfo = (info:any): info is SmallInfo => {
	return !!info.smallPhoto
}

const infoArr: (BigInfo | SmallInfo)[] = [...];
                                      
return <List>
    {infoArr.map((item)=>{
    	if(isBigInfo(item)){
            // 이 스코프에서 item의 타입은 BigInfo.
        	return <BigCard card={item}/>
        }
        if(isSmallInfo(item)){
            // 이 스코프에서 item의 타입은 SmallInfo.
            return <SmallCard card={item}/>
        }
        return null
    })}                                      
</List>

코드도 훨씬 깔끔해졌고, 확장성에도 훨씬 자유로워졌다.
is는 이런 케이스 외에도 다양하게 활용 가능하다. webview 환경에서 message로 통신할 때 앱과 웹 사이드에서 미리 정해둔 메시지 형식이 있다고 가정해보자. 기본적으로 웹-앱간 주고받는 message는 string 타입을 갖고, 이는 object 타입을 직렬화한 형태일 확률이 높다. 이렇게 애매모호하고 어떤 값이 들어올지 모르는 환경에서는 어디서 문제가 생길지 몰라 굉장히 불안할 수밖에 없다. 이럴 때 아래와 같이 미리 타입을 선언해두고 is로 판단함으로써 사이드이펙트를 최소화할 수 있다.

type WebViewMssage = {
	TALK: {
    	eventName: 'TALK';
        data: {status: boolean; message?: string};
    }
}

const isTalkMessage = (message:any): message is WebViewMssage['TALK'] => {
	return message?.eventName === 'TALK'
}
    
const onMessage = (event: WebViewMessageEvent) => {

    const message = JSON.parse(event.nativeEvent.data);
    if(isTalkMessage(message)){
    	// 이 스코프에서 message는 WebViewMssage['TALK']의 타입을 가짐
        // evnetName과 data의 타입을 특정할 수 있으므로 ide의 도움을 받아 실수할 여지를 줄여준다.
        ...
    }
}
profile
React-Native 개발블로그

4개의 댓글

comment-user-thumbnail
2024년 4월 23일

언노운 쓰십쇼

1개의 답글