[유틸리티 타입을 알아보자]에서 유틸리티 타입에는 무엇이 있는지, 어떻게 사용하는지 알아봤다. 이번에는 실제로 어떤 상황에서 이런 타입들을 사용할 수 있는지 알아보자. 사례를 알아보려고 한다.
value가 모두 string인 object의 타입을 할당하려고 할 때 보통 {[k:string]:string} 형태로 선언해 사용하고는 한다. 이 방법도 물론 잘 작동하지만, Record을 사용하면 보다 더 적절한 형태로 사용할 수 있다.
const goodWords = {
a:'good',
b:'cool',
c:'nice',
}
const happy = (compliment:{[k:string]:string}) =>{
console.log(compliment.a)
}
const pleasure = (compliment:Record<keyof typeof goodWords, string>) =>{
console.log(compliment.a)
}
미리 선언된 goodWords object를 parameter로 받는 두개의 함수 happy와 pleasure를 정의했다. 두 함수는 동일한 작업을 수행하지만 parameter의 타입을 각각 다른 방법으로 선언했다. 이렇게 작성하면 두 케이스 모두 에러없이 잘 작동한다. 하지만 실제로는 Record를 사용했을 때 더 편리한데, 바로 자동완성 기능 때문이다. 모든 key와 value가 string타입이라고 일괄적으로 선언해버리는 happpy의 compliment와는 달리 pleasure의 compliment는 key가 각각 선언되어 있기 때문에 pleasure를 사용할 때 IDE 단에서 어떤 아이템이 있는지 알려주게 된다.
emotion이나 styled-component를 쓰다보면 css 코드 작성 중 props를 사용할 일이 많아진다. 이 때 각 component마다 필요한 props는 몇개 안되지만, 여러개의 component를 한번에 정의하다보면 type들이 쌓이게 되는 경우가 생긴다. 이런 경우에 재사용성을 위해 styledPorps로 type을 선언해 사용하고는 했는데, 이 시점에 유틸리티 타입을 함께 사용하면 효과를 극대화 할 수 있다.
const SomeView = styled.View`
background-color: ${(props: {color: string}) => props.color};
...
`;
const OtherView = styled.View`
width: ${(props: {width:number}) => props.width}px;
...
`;
const AnotherView = styled.View`
background-color: ${(props: {isFocused: boolean}) =>
props.isFocused ? 'black' : 'white'};
...
`;
이렇게 서로다른 3개의 스타일 컴포넌트에서 각각 한 개의 props만을 사용하지만, 그로인해 하드코딩으로 3번의 타입을 선언해주어야 했다. 만약 더 많은 컴포넌트를 정의하면서 동일한 타입을 여러번 선언해줘야하는 상황이라면 굉장히 비효율적일 것이다.
interface StyledProps {
width: number;
color: string;
isFocused: boolean;
}
const SomeView = styled.View`
background-color: ${(props: StyledProps) => props.color};
...
`;
...
return <>
...
<SomeView color="green"/>
...
</>
이 문제를 해결하기 위해 StyledProps라는 새로운 타입을 선언해서 사용하려고 했지만 이런 에러를 만났다.
Type '{ color: string; }' is missing the following properties from type 'StyledProps': width, isFocused
SomeView의 props는 color,width,isFocused가 모두 필요한데 color만 입력했기 때문에 발생한 에러였다. 문제가 명확한 만큼 해결하는 방법도 여러가지가 있을 수 있다. 애초에 StyledProps의 각 필드를 모두 옵셔널로 선언하는 방법이 있을 수 있고, Partial을 사용할 수도 있을 것이다.
interface StyledProps {
width?: number;
color?: string;
isFocused?: boolean;
}
OR
const SomeView = styled.View`
background-color: ${(props: Partial<StyledProps>) => props.color};
...
`;
혹은 Pick을 사용해 필요한 type만 가져올 수도 있다.
const SomeView = styled.View`
background-color: ${(props: Pick<StyledProps,"color">) => props.color};
...
`;
어떤 방법을 써도 무방하지만, 나는 최종적으로 Pick을 사용해 구현하는 것으로 결정했다.
const logItem = (times:number,text:string)=>{
for(let i=0; i<times; i++){
console.log(text)
}
}
logItem(2,"hi")
//"hi"
//"hi"
횟수와 문자열을 받아 횟수만큼 text를 출력해주는 logItem함수를 만들었다. 이 경우 times는 number로 들어올 것이라는 것을 쉽게 추론할 수 있다. 하지만 프로젝트를 진행하면서 변경사항이 생겼다. logItem의 times를 직접 넣어주는게 아니라, 굉장히 복합적인 로직을 통해 적절한 값을 리턴해주는 getTimes의 결과값을 사용하기로 했다. 또한 항상 number를 리턴한다고 보장하지도 않는다. 이럴경우 어떻게 할 수 있을까? 직접 getTimes 코드를 보며 경우의 수를 고려하고 모든 타입을 지정해줄 수도 있겠다 하지만 유틸리티 타입을 쓰면 훨씬 편하게 문제를 해결할 수 있다.
const getTimes = (...someParams) =>{
return (...whatever)
}
const logItem = (times:ReturnType<typeof getTimes>,text:string)=>{
(...typeGuardCode)
for(let i=0; i<times; i++){
console.log(text)
}
}
유틸리티 타입을 공부하면서 당장 떠오른 3개의 케이스를 정리해봤는데, 아마 더 찾아보면 그 활용 가능성이 무궁무진하지 않을까 싶다. 지금까지는 유틸리티 타입을 써본적도 없었고 딱히 필요성도 느끼지 못하고 있었는데, 이번에 시간 내서 한번 배워보니 진작 썼으면 조금 더 쉽게 타입스크립트를 활용할 수 있지 않았을까하는 아쉬움이 느껴졌다. 역시 사람은 더 편한걸 가져다 줘야 이전에 불편했던 것을 체감하나 보다. 더 공부하고 적극적으로 연습하면서 하루 빨리 TS를 자유자재로 쓰는 날이 오기를!