타입스크립트(TypeScript)를 처음 접하면 가장 먼저 배우는 단어가 있습니다. 바로 any입니다. 어떤 타입이든 허용한다는 마법 같은 단어지만, 아이러니하게도 any를 남발하는 순간 우리는 타입스크립트를 사용하는 가장 큰 이유인 '타입 안전성'을 포기하게 됩니다.
실무에서 외부 API 응답이나 동적 데이터를 다룰 때, 우리는 데이터의 정확한 형태를 알 수 없는 상황에 직면합니다. 이때 습관적으로 any를 쓰기보다 더 안전하고 견고한 unknown 타입을 선택해야 합니다. 오늘은 왜 unknown이 any보다 우월한지, 그리고 unknown 데이터를 안전하게 요리하는 타입 가드(Type Guard) 기법을 상세히 알아보겠습니다.
1. any: 모든 안전장치를 해제하는 위험한 열쇠
any는 타입스크립트의 타입 검사기(Type Checker)를 완전히 무력화합니다.
let value: any = 10;
value.toUpperCase(); // 컴파일 타임에 에러가 발생하지 않음 -> 런타임에 에러 발생!
위 코드에서 value는 숫자지만 any 타입이기 때문에 문자열 메서드인 toUpperCase 호출을 허용합니다. 결국 이 오류는 사용자가 앱을 실행하는 도중 "Uncaught TypeError"라는 이름으로 터지게 됩니다. 사실상 자바스크립트를 쓰는 것과 다를 바 없는 상태가 되는 것이죠.
2. unknown: "무엇인지 모르니 확인하고 쓰세요"
unknown 타입은 any와 마찬가지로 모든 값을 할당받을 수 있습니다. 하지만 결정적인 차이점은 '사용할 때' 발생합니다.
2.1 unknown의 제약 사항
unknown 타입으로 정의된 변수는 타입을 정적으로 확정하기 전까지는 어떠한 동작도 수행할 수 없습니다.
let value: unknown = 10;
// 에러 발생: 'value'의 형식이 'unknown'입니다.
value.toUpperCase();
// 타입을 확인해야만 사용 가능
if (typeof value === "string") {
console.log(value.toUpperCase()); // 이제 안전하게 사용 가능!
}
이처럼 unknown은 개발자에게 "이 데이터의 타입을 먼저 검증하라"라고 강제합니다. 컴파일러가 우리 대신 안전장치를 한 번 더 확인해 주는 셈입니다.
3. unknown을 안전하게 다루는 법: 타입 가드(Type Guard)
unknown 데이터의 정체를 밝히기 위해 사용하는 기법이 바로 타입 가드입니다.
3.1 typeof 연산자 (기본 타입 검사)
숫자, 문자열, 불리언 등 자바스크립트의 기본 타입을 확인할 때 사용합니다.
3.2 instanceof 연산자 (클래스/객체 검사)
특정 클래스의 인스턴스인지 확인할 때 유용합니다.
function processDate(input: unknown) {
if (input instanceof Date) {
console.log(input.toISOString()); // Date 객체임이 보장됨
}
}
3.3 사용자 정의 타입 가드 (Type Predicates)
가장 강력하고 실무에서 많이 쓰이는 방식입니다. is 키워드를 사용하여 함수가 특정 타입임을 명시적으로 알려줍니다.
interface User {
name: string;
age: number;
}
// User 타입인지 확인하는 가드 함수
function isUser(target: any): target is User {
return (
typeof target === "object" &&
target !== null &&
"name" in target &&
"age" in target
);
}
function greet(input: unknown) {
if (isUser(input)) {
console.log(`안녕하세요, ${input.name}님!`); // 여기서 input은 자동으로 User 타입이 됨
}
}
4. 실무 예시: API 에러 처리
API 통신 중 발생하는 에러(catch 문의 error 객체)는 기본적으로 unknown 타입(TS 4.4 이상)입니다. 이때 unknown과 타입 가드를 활용하면 매우 견고한 에러 처리가 가능합니다.
try {
await fetchData();
} catch (error: unknown) {
if (error instanceof AxiosError) {
// axios 에러인 경우 처리
console.error(error.response?.data.message);
} else if (error instanceof Error) {
// 일반 에러인 경우 처리
console.error(error.message);
} else {
// 그 외의 경우
console.error("알 수 없는 에러 발생", error);
}
}
이전처럼 (error as any). message라고 썼을 때 발생할 수 있는 런타임 폭탄을 원천 차단할 수 있습니다.
5. any는 절대 쓰면 안 되나요?
현실적으로 any가 필요한 순간도 있습니다.
- 마이그레이션 중인 프로젝트에서 타입을 일일이 지정하기 벅찰 때.
- 타입 정의가 너무 복잡하여 생산성을 심각하게 해칠 때.
- 제네릭 등으로 도저히 해결 안 되는 복잡한 고차 함수를 짤 때.
하지만 이는 어디까지나 '임시방편'이어야 합니다. any를 썼다면 반드시 TODO 주석을 남기고 나중에 적절한 타입으로 교체해야 합니다.
'개발' 카테고리의 다른 글
| [TS] 제네릭(Generic)을 활용한 재사용 가능한 고차 컴포넌트(HOC) 설계 (0) | 2026.04.27 |
|---|---|
| [TS] Utility Types 정복하기: Pick, Omit, Partial로 중복 없는 타입 정의 (0) | 2026.04.27 |
| [Architecture] 프론트엔드 클린 아키텍처: 도메인 로직과 UI 컴포넌트 분리하기 (0) | 2026.04.27 |
| [React] 선언적 프로그래밍의 정수: Suspense와 ErrorBoundary로 우아한 UI 만들기 (0) | 2026.04.27 |
| [React] Props Drilling을 피하는 3가지 방법: Composition vs Context vs Zustand (0) | 2026.04.27 |