TypeScript 'Type is not assignable' 에러 원인 및 해결법
- 원인: 넓은 타입(
string)을 좁은 타입(유니온 리터럴)에 그대로 대입하려 하거나, 정의에 없는 잉여 프로퍼티가 든 객체 리터럴을 직접 대입해서 발생 - 해결:
as로 좁혀서 단언 / 객체는 중간 변수에 담았다가 대입 / 유니온 타입은typeof·in으로 분기해서 좁히기 - 주의:
as단언은 컴파일러 검사를 우회하는 것이라 실제로 그 타입이 맞는지는 개발자가 책임져야 함
userStatus = inputStatus as UserStatus; // string -> 리터럴 유니온
↓ 왜 이런 에러가 나는지 원리 보기
타입스크립트 코딩을 할 때 가장 자주 마주치는 경고가 "Type 'A' is not assignable to type 'B'"(A 타입을 B 타입에 할당할 수 없습니다)일 겁니다. 언뜻 데이터 구조가 똑같아 보이는데 왜 불일치 경고가 나는지, 실전에서 타입을 좁혀 해결하는 방법을 정리했습니다.
왜 'not assignable' 에러가 발생하는가
이 에러의 본질은 "타입 호환성(Type Compatibility)의 규칙 위반"에 있습니다. 타입스크립트는 정적 컴파일 단계에서 값이 안전하게 대입될 수 있는지 검사합니다. 상위 개념의 넓은 타입에 하위 개념의 구체적인 타입을 넣는 것은 안전하지만(예: string에 "admin" 리터럴 대입), 반대로 더 넓은 범주의 타입을 구체적인 타입 칸에 강제로 집어넣으려고 할 때 컴퓨터는 런타임 크래시를 우려해 할당 거부 경고를 내보냅니다. API 응답 값을 그대로 유니온 타입 변수에 넣으려다가 이 에러를 처음 마주치는 경우가 많습니다.
타입 할당 가능 범위 시각화
(예:
"admin" 리터럴 타입을 string 타입 변수에 대입)
(예: 일반
string 타입을 "admin" 전용 변수에 대입 시 에러)
대표적인 2가지 오류 상황과 해결 기법
1. 유니온 타입(Union Type)과 리터럴 할당 에러
특정 지정 문자열 목록만을 허용하는 유니온 타입에 일반 string 변수를 대입하려 할 때 발생합니다.
[잘못된 예시]
type UserStatus = "active" | "suspended" | "inactive";
let inputStatus: string = "active"; // 일반 string 타입
let userStatus: UserStatus;
// 에러: Type 'string' is not assignable to type 'UserStatus'.
userStatus = inputStatus;
[올바른 해결 코드]
type UserStatus = "active" | "suspended" | "inactive";
let inputStatus: string = "active";
let userStatus: UserStatus;
// 해결책 A: 'as' 키워드로 타입을 좁혀 할당하기
userStatus = inputStatus as UserStatus;
// 해결책 B: 애초에 변수 타입을 리터럴로 선언하기
const fixedStatus: UserStatus = "active";
userStatus = fixedStatus;
2. 객체 매핑 시 '초과 프로퍼티 검사' 우회
객체 리터럴을 변수에 직접 할당할 때, 정의된 타입 사양에 없는 잉여 프로퍼티가 있으면 오류가 터집니다.
[잘못된 예시]
interface Point {
x: number;
y: number;
}
// 에러: Object literal may only specify known properties, and 'z' does not exist...
const myPoint: Point = { x: 10, y: 20, z: 30 };
[올바른 해결 코드]
interface Point {
x: number;
y: number;
}
const rawData = { x: 10, y: 20, z: 30 };
// 중간 변수(rawData)를 통해 대입하면 구조적 타이핑에 의해 안전하게 할당됩니다.
const myPoint: Point = rawData;
타입스크립트 실전 타입 좁히기(Narrowing) 기법
방법 A: typeof를 사용한 원시 타입 분기
function printLength(input: string | number) {
if (typeof input === "string") {
// string 타입으로 자동 좁히기
console.log(input.length);
} else {
// number 타입으로 자동 좁히기
console.log(input.toFixed(2));
}
}
방법 B: in 연산자를 활용한 커스텀 객체 식별
interface Admin { privileges: string[]; }
interface Employee { startDate: Date; }
function checkUser(user: Admin | Employee) {
if ("privileges" in user) {
// Admin 타입으로 타입 자동 좁히기 완료
console.log(user.privileges);
} else {
// Employee 타입으로 타입 자동 좁히기 완료
console.log(user.startDate);
}
}
결제 수단이 카드/계좌이체로 나뉘는 화면을 만들 때, 두 타입을 유니온으로 묶어두고 payment.cardNumber처럼 한쪽에만 있는 필드에 바로 접근하려다 컴파일 에러를 여러 번 받은 적이 있습니다. 처음엔 그냥 as로 단언해서 넘어갔는데, 나중에 실제로 계좌이체 데이터가 들어왔을 때 cardNumber가 undefined로 조용히 새는 버그가 났습니다. in 연산자로 분기하도록 바꾼 뒤로는 그런 종류의 실수가 컴파일 단계에서 걸러졌습니다.
요약: 타입 불일치 에러 대응 체크리스트
넓은 범주의 값을 좁은 타입 칸에 대입하려 할 때는 as 키워드로 구체적인 타입임을 단언해 주는 것이 기본입니다. 객체 리터럴을 직접 대입하는데 정의되지 않은 초과 필드가 있다면, 중간 객체 변수에 한 번 담았다가 간접적으로 대입하는 우회 방식이 잉여 프로퍼티 검사를 피하는 방법입니다. 유니온 타입의 속성을 참조할 때는 typeof나 in 연산자로 타입을 분기해 좁혀두면 공통되지 않은 프로퍼티 접근으로 인한 오류를 막을 수 있습니다.
타입 호환성도 결국 자바스크립트의 기본 자료형들이 서로 어떻게 다른지 아는 데서 출발합니다. 기초가 헷갈리신다면 [호마다의 IT 개발 입문 블로그] 의 자료형 기초 글도 참고해보세요.
댓글
댓글 쓰기