타입 단언(Type Assertion) — as와 non-null assertion
타입 단언(Type Assertion)은 개발자가 TypeScript에게 "이 값의 타입은 내가 알고 있어"라고 알려주는 것 입니다. 타입을 변환하는 게 아닙니다.
as 키워드
// DOM 요소 가져올 때 가장 많이 사용
const input = document.getElementById('username');
// input: HTMLElement | null
// as로 더 구체적인 타입을 단언
const inputEl = document.getElementById('username') as HTMLInputElement;
inputEl.value = '홍길동'; // OK — HTMLInputElement의 속성에 접근 가능
앵글 브래킷 문법 (JSX에서 사용 불가)
// 앵글 브래킷 문법 — JSX 파일(.tsx)에서는 사용 불가
const inputEl = <HTMLInputElement>document.getElementById('username');
// TSX에서는 반드시 as를 사용
const inputEl2 = document.getElementById('username') as HTMLInputElement;
React/Next.js 프로젝트에서는 항상 as를 쓰므로, as 문법만 익히면 됩니다.
타입 단언의 제약
TypeScript는 말도 안 되는 단언 은 막아 줍니다.
// ❌ string을 number로 단언 불가
// const value = 'hello' as number; // Error
// 두 타입 사이에 충분한 관련성이 있어야 단언 가능
const value = 'hello' as string; // OK (동일 타입)
const elem = document.createElement('div') as HTMLDivElement; // OK (상속 관계)
이중 단언(Double Assertion)
정말로 강제 변환이 필요할 때 unknown을 거치면 가능합니다.
// ⚠️ 이중 단언 — 권장하지 않지만 가끔 필요
const value = 'hello' as unknown as number;
// 실전 예시: 테스트에서 mock 객체 만들 때
interface ComplexService {
fetchData(): Promise<string[]>;
processData(data: string[]): void;
cleanup(): void;
}
// 모든 메서드를 구현하기 귀찮을 때 (테스트에서만!)
const mockService = {
fetchData: jest.fn(),
} as unknown as ComplexService;
이중 단언은 타입 안전성을 완전히 포기하는 것이므로, 테스트 코드 에서만 사용하는 것을 권장합니다.
Non-null Assertion (!)
값이 null이나 undefined가 아님을 단언합니다.
// element는 HTMLElement | null
const element = document.getElementById('app');
// ❌ null일 수 있으므로 에러
// element.textContent = 'hello'; // Error
// ✅ non-null assertion — "null이 아님을 보장한다"
element!.textContent = 'hello'; // OK
// ⚠️ 하지만 런타임에 null이면 에러 발생!
! 보다 안전한 대안
// 방법 1: if 체크
const element = document.getElementById('app');
if (element) {
element.textContent = 'hello'; // OK — null이 제거됨
}
// 방법 2: 옵셔널 체이닝
element?.textContent; // null이면 undefined 반환
// 방법 3: nullish coalescing과 조합
const text = element?.textContent ?? '기본값';
면접 포인트: "non-null assertion은 언제 쓰나요?"라고 물어보면, "TypeScript가 null 가능성을 감지하지만 개발자가 확실히 null이 아님을 알 때 사용합니다. 하지만 가능하면 타입 좁히기로 대체하는 것이 안전합니다"라고 답하면 됩니다.
const assertion (as const)
as const는 타입 단언과 이름이 비슷하지만, 동작은 다릅니다.
// as const — 값을 읽기 전용 리터럴로 좁히기
const config = {
method: 'GET' as const, // 'GET' 리터럴 타입
retries: 3 as const, // 3 리터럴 타입
};
// 객체 전체에 적용
const fullConfig = {
method: 'GET',
retries: 3,
} as const;
// { readonly method: 'GET'; readonly retries: 3 }
as const는 별도의 편에서 자세히 다룹니다.
타입 단언 vs 타입 선언
interface User {
name: string;
age: number;
}
// 타입 선언 — 초과 속성 검사가 동작
// const user: User = { name: '홍길동', age: 25, email: 'test' }; // ❌ Error
// 타입 단언 — 초과 속성 검사를 우회
const user = { name: '홍길동', age: 25, email: 'test' } as User; // OK ⚠️
// 타입 단언 — 필수 속성이 빠져도 에러 안 남
const partial = {} as User; // OK ⚠️ — name, age가 없는데 에러 없음
공부하다 보니 타입 단언이 초과 속성 검사를 우회 한다는 점을 놓치는 경우가 많더라고요. 가능하면 타입 선언(:)을 쓰는 것이 더 안전합니다.
satisfies 연산자 (TS 4.9+)
satisfies는 타입을 검증하면서도 추론을 유지하는 연산자입니다.
type ColorMap = Record<string, string | number[]>;
// as — 타입을 강제로 바꿈 (추론 정보 손실)
const colors1 = {
red: '#ff0000',
green: [0, 255, 0],
} as ColorMap;
// colors1.red: string | number[] — 어떤 것인지 모름
// satisfies — 타입을 검증하되 추론은 유지
const colors2 = {
red: '#ff0000',
green: [0, 255, 0],
} satisfies ColorMap;
// colors2.red: string — 구체적인 추론 유지
// colors2.green: number[] — 구체적인 추론 유지
satisfies는 별도의 편에서 자세히 다룹니다.
실전에서의 사용 가이드
// ✅ 적절한 사용
// 1. DOM 요소 타입 좁히기
const canvas = document.getElementById('canvas') as HTMLCanvasElement;
// 2. API 응답 타입 지정 (타입 가드와 함께)
const response = await fetch('/api/user');
const data = (await response.json()) as User;
// ❌ 피해야 할 사용
// 1. 타입 에러를 무시하기 위한 단언
const wrong = someValue as any; // 근본적인 타입 문제를 숨김
// 2. 빈 객체를 특정 타입으로 단언
const empty = {} as ComplexType; // 런타임에 속성 접근 시 에러
정리
as는 개발자가 TypeScript보다 타입을 더 잘 알 때 사용한다- 이중 단언(
as unknown as T)은 테스트에서만 허용하자 !(non-null assertion)보다 타입 좁히기가 더 안전하다- 타입 단언은 초과 속성 검사를 우회하므로, 가능하면
: Type을 사용하자 satisfies는 검증과 추론을 동시에 할 수 있는 현대적 대안이다
댓글 로딩 중...