타입 좁히기 (Type Narrowing)
참고 문서
타입 좁히기란, 하나의 값이 여러 타입 중 하나일 때 코드의 흐름(조건문, 검사 등)을 통해 보다 구체적인 타입으로 한정하는 과정이다.
주로 typeof
, instanceof
, 동등 비교, in
연산자, 사용자 정의 타입 가드 등을 통해 수행된다. 유니언 타입 |
을 다룰 때 특히 자주 사용된다.
1. typeof 연산자
typeof는 기본형(primitive)
타입 구분에 사용된다.
JS의 런타임 타입 검사와 동일하게 동작하며, 조건문 내부에서 TypeScript가 자동으로 타입을 좁혀준다.
ts
type Id = number | string;
let id: Id = 1;
if (typeof id === "string") {
// 문자열 ID 경우 로직 처리
}
function getId(id: Id) {
if (typeof id === "number") {
return id;
}
return Number(id);
}
getId(1);
getId("1");
typeof
는 실행 시점에 실제 값의 타입을 판별한다.null
은 예외적으로object
를 반환하므로 주의
2. 동일성/동등성 좁히기 (Equality Narrowing)
값의 동등 비교 ===
, !==
를 통해 타입을 구분할 수 있다.
리터럴 유니언("on" | "off")
타입에서 자주 사용된다.
ts
type Option = "on" | "off";
function power(option: Option) {
if (option === "off") {
console.log("power off");
} else {
console.log("power on");
}
}
power("on");
power("off");
- 비교문 안에서 option의 타입이 off 또는 on으로 좁혀진다.
3. in 연산자
in 연산자는 객체에 특정 속성이 존재하는지를 확인해 타입을 좁힌다.
서로 다른 객체 구조를 가진 유니언 타입에서 자주 쓰인다.
ts
type iOS = { iMessage: () => void };
type android = { message: () => void };
function sendMessage(os: iOS | android) {
if ("iMessage" in os) {
os.iMessage(); // iOS로 좁혀진다
} else {
os.message(); // android 케이스
}
}
// iOS 타입
sendMessage({
iMessage: () => {
console.log("sending iMessage");
},
});
// android 타입
sendMessage({
message: () => {
console.log("sending message");
},
});
- in 연산자는 객체에 해당 프로퍼티가 존재하면 true를 반환하므로, 구조가 다른 API 응답 타입을 구분할 때도 자주 쓰인다.
4. instanceof Narrowing
instanceof
는 클래스(또는 생성자 함수) 기반 객체를 구분할 때 사용한다.
ts
class ApiResponse {
data: any;
}
class ErrorResponse {
message: string;
}
async function handleApiResponse(response: any) {
if (response instanceof ApiResponse) {
// 데이터 처리
} else if (response instanceof ErrorResponse) {
// 에러 처리
}
}
const apiResponse = new ApiResponse();
const errorResponse = new ErrorResponse();
handleApiResponse(apiResponse);
handleApiResponse(errorResponse);
instanceof
는 클래스나 함수로 생성된 객체에만 사용할 수 있다. 인터페이스(interface) 타입에는 적용되지 않는다.
5. 타입 가드 (Type Predicates)
사용자 정의 타입 가드 함수는, 복잡한 타입 분기 로직을 함수 형태로 추출해 재사용할 수 있다.
ts
function isErrorResponse(
response: ApiResponse | ErrorResponse
): response is ErrorResponse {
return (response as ErrorResponse).message !== undefined;
}
const response = { message: "error.." };
if (isErrorResponse(response)) {
// 에러케이스
console.log(response.message);
}
- 함수의 반환 타입
response is ErrorResponse
는 TypeScript에게 직접 타입을 좁히는 규칙을 알려주는 문법이다.
6. 식별 가능한 유니언 타입 (Discriminated Union)
공통된 리터럴 속성(type, kind, status 등)을 기준으로 유니언 타입을 안전하게 구분하는 패턴이다. TypeScript의 가장 대표적인 타입 좁히기 기법이다.
ts
type SuccessResponse = {
type: "success";
data: any;
};
type ErrorResponseType = {
type: "error";
message: string;
};
type ApiResponseType = SuccessResponse | ErrorResponseType;
function handleResponse(response: ApiResponseType) {
if (response.type === "success") {
console.log("data:", response.data);
} else {
console.log(response.message);
}
}
response.type
값이 success 또는 error로 좁혀지므로, 조건문 안에서 자동 완성과 타입 안전성이 확보된다.