타입스크립트 Union 타입 좁히기 패턴
문제 상황
레거시 API가 성공/실패 여부에 따라 다른 응답 구조를 반환하는데, 이를 처리하는 코드가 타입 체크를 제대로 받지 못하고 있었다.
interface SuccessResponse {
data: User[];
total: number;
}
interface ErrorResponse {
error: string;
code: number;
}
type ApiResponse = SuccessResponse | ErrorResponse;
문제는 response.data에 접근할 때마다 타입스크립트가 에러를 냈고, 결국 as any로 도배하게 됐다는 점이다.
Discriminated Union 적용
공통 속성을 추가해 타입을 구분할 수 있도록 수정했다.
interface SuccessResponse {
success: true;
data: User[];
total: number;
}
interface ErrorResponse {
success: false;
error: string;
code: number;
}
type ApiResponse = SuccessResponse | ErrorResponse;
function handleResponse(response: ApiResponse) {
if (response.success) {
// 여기서 response는 자동으로 SuccessResponse 타입
console.log(response.data.length);
} else {
// 여기서는 ErrorResponse 타입
console.error(response.error);
}
}
success 필드 하나로 타입 가드가 자동으로 작동한다. 조건문 내부에서 타입스크립트가 타입을 정확히 추론해준다.
Type Predicate 함수
반복적으로 사용되는 타입 가드는 별도 함수로 분리했다.
function isSuccess(response: ApiResponse): response is SuccessResponse {
return response.success === true;
}
if (isSuccess(response)) {
// SuccessResponse로 좁혀짐
return response.data;
}
is 키워드를 사용한 type predicate 덕분에 함수 호출만으로 타입 좁히기가 가능해졌다.
결과
any사용 12곳 제거- 런타임 에러 가능성 있던 부분 컴파일 타임에 감지
- 코드 가독성 개선
Union 타입은 공통 속성(discriminant)만 잘 설계하면 타입 안정성을 크게 높일 수 있다.