타입스크립트에서는 조건에 따라 다른 로직을 실행할 수 있다.
Condition ? true : false
삼항연산자를 활용한 조건부 로직
그렇다면, 타입도 조건에 따라 다르게 할당할 수 있을까?
예를들면, Condition이 true면 AType, false면 BType 처럼 말이다.
조건부 타입이 필요한 상황
타입에도 조건부 를 부여할 수 있다.
즉 , 조건부 타입이 가능하다.
현업에서 조건부 타입이 필요했던 상황과 활용방법에 대해 알아보자.
요구사항
요구사항은 버스와 지하철, 사무기관의 정보를 보여주는 화면을 개발하는 것이였다.
서버에서 내려주는 spec은 아래와 같았다.
type Bus = {
name: string
lat: number
lng: number
}
type Subway = {
name: string
line: string
lat: number
lng: number
}
type Government = {
name: string
lat: number
lng: number
}
`Subway` 만 제외하면 `name`, `lat`, `lng` field가 공통으로 내려온다.
api path는 아래와 같다.
// api path
const busApiPath = '/facility/bus'
const subwayApiPath = '/facility/subway'
const governmentApiPath = '/facility/government'
suffix 로 `/facility/~` 로 시작하며 포맷또한 동일하다.
당시에 나는 버스, 지하철, 사무기관의 정보를 가져오는 hook을 각각 만들었고, hook을 호출해 화면을 구현했다.
const bus = useGetBus() // Bus[] | undefind
const subway = useSubway() // Subway[] | undefind
const government = useGovernment() // Government[] | undefind
중복코드를 제거할 수 있지 않을까?
버스와 지하철, 사무기관은 api spec이 매우 유사해서 굳이 hook을 각각 만들 필요가 있나라는 생각이 들었다.
그래서 고안해 본 코드는 아래와 같다.
type Facility = 'bus' | 'subway' | 'government'
const useFacility = (type: Facility): Bus[] | Subway[] | Government[] | undefined => {
const data = useGet(`/facility/${type}`)
return data
}
type을 버스, 지하철, 사무기관으로 구분했다.
그리고, 사용자가 필요로 하는 데이터에 맞는 타입을 hook에 매개변수로 넘기면 그 타입에 맞는 api를 호출하고 리턴하도록 했다.
리턴하는 타입은 당연히 버스/지하철/사무기관 일 수 있으므로 유니온으로 결합했다.
const bus = useFacility('bus')
const subway = useFacility('subway')
const government = useFacility('government')
짠! 기존엔 세개의 hook(useGetBus, useGetSubway, useGetGovernment) 파일이 별도로 있었는데,
useFacility hook 하나만으로 중복코드 제거에 성공했다!
문제발생
하지만, 늘 그렇듯, 마음대로 되지 않는다.
애써 중복코드를 제거하기 위해 하나의 hook으로 호출할 수 있도록 고안했으나,
결국 Hook을 사용하는 쪽에서 해당 데이터의 타입을 좁혀가는 과정이 필요했다.
타입가드를 사용하자!
맞다. 타입가드를 사용하면 된다.
const isBus = (data:Bus[] | Subway[] | Government[] | undefined) : data is Bus[] => {
return type === 'bus'
}
const isSubway = (data:Bus[] | Subway[] | Government[] | undefined) : data is Subway[] => {
return type === 'subway'
}
const isGovernment = (data:Bus[] | Subway[] | Government[] | undefined) : data is Government[] => {
return type === 'government'
}
그러면 이와같은 타입명제 함수를 만들고 사용하는데...
애써 hook을 간추렸더니, 그걸 사용하려면 복잡하게 타입을 검사해야한다는 점이 아쉬웠다.
그리고 만약 하나의 타입이 또 추가된다면 ? 또 그 타입에 대한 타입명제 함수를 추가해줘야 한다.
이 상황을 해소시켜줄 수 있는 방법은 조건부 타입을 사용하는 것이다.
조건에 따라 타입을 제한하는 것이다.
조건부 타입 사용하기
서론이 길었다. 조건부타입은 아래처럼 활용한다.
type T<A> = A extends B ? OneType : TwoType
A가 B를 만족하면 T는 OneType이고, 아니면 TwoType이라는 의미이다.
예제 코드를 보자.
type Base = {
name: string
lat: number
lng: number
}
type FacilityObject<Type> = Type extends 'subway' ? Subway : Base
일단 버스와 사무기관은 타입이름만 다르지 프로퍼티들은 동일하기 때문에 `Base` 라는 이름으로 사용했다.
단, 지하철은 line 프로퍼티가 있는 상태이므로 Subway를 그대로 사용했다.
FacilityObjecy 타입은 Type이 subway면 Subway 타입, 그외면 Base타입이라는 의미이다.
const useFacility = <Type extends Facility>(type: Type): FacilityObject<Type>[] | undefined => {
...
}
그리고 이 타입을 앞서 만든 Hook에 사용하면,
이렇게 사용자가 넘긴 타입에 따라 자동으로 데이터 타입이 추론되는 것을 볼 수 있다.
결론
타입스크립트에서의 타입은 컴파일단계에서만 존재하기 때문에 런타임에서는 무용지물이다.
그래서 런타임환경에서 타입을 검사하기 위해서는 여러가지 타입 가드 방법을 통해 추론해나가야 한다.
조건부 타입도 그 추론방식 중 하나인데, 최근에야 알게 된 방법이라 아쉽다.
조금 더 빨리 알았더라면 더 좋은 코드를 짤 수 있지 않을까 싶다.
참고서적
우아한 타입 스크립트 with 리엑트 - 우아한 형제들
'🌳Frontend > typescript' 카테고리의 다른 글
Typescript 에서 string union type을 type guard 하는 법 (typeof Array[number] 와 as const) (0) | 2023.09.17 |
---|---|
[Typescript/TS] 배열에서 union type 만들기 (0) | 2023.07.13 |
[타입스크립트/Typescript] clean code 클린코드 (0) | 2023.03.21 |