라떼군 이야기


TypeScript에서 "Unsafe assignment of an error typed value" ESLint 에러 원인과 해결 방법

Problem

TypeScript 프로젝트에서 객체의 속성을 동적인 키(예: 난수나 변수)로 접근하여 특정 타입의 변수에 할당할 때 종종 ESLint 에러를 마주하게 됩니다. 특히 @typescript-eslint/no-unsafe-assignment 규칙이 활성화되어 있다면, "Unsafe assignment of an error typed value"라는 모호한 에러 메시지가 발생합니다. 개발자 입장에서는 기껏해야 해당 값이 undefined일 것이라 예상하지만, 린터는 이를 ‘안전하지 않은 할당’으로 간주하여 빨간 줄을 표시하고 빌드를 실패하게 만듭니다.

Background

이 문제의 근본적인 원인은 TypeScript의 타입 추론 방식과 ESLint의 엄격한 타입 검사 규칙 간의 상호작용에 있습니다. 객체를 리터럴로 선언하면, TypeScript는 명시된 키와 값만을 바탕으로 좁은 범주의 타입을 추론합니다. 이후 런타임에 결정되는 동적인 키(예: number 타입의 변수)로 객체에 접근하면, TypeScript 컴파일러는 해당 키가 객체에 존재할지 확신할 수 없어 결과값을 any 타입으로 평가합니다. ESLint의 no-unsafe-assignment 규칙은 이렇게 암묵적으로 any로 평가된 값을 명시적인 타입(string 등)이 지정된 변수에 할당하는 것을 감지하고, 개발자가 의도치 않은 타입 오류를 범하는 것을 막기 위해 경고를 발생시키는 것입니다.

Solution

이 문제를 해결하는 방법은 크게 두 가지가 있습니다. 코드의 타입을 명확히 하거나, IDE의 일시적인 오류를 해결하는 것입니다.

1. Record 유틸리티 타입을 사용한 명시적 타입 지정 (권장)

가장 올바른 해결책은 객체가 어떤 형태의 키와 값을 가질 수 있는지 TypeScript에게 명확히 알려주는 것입니다. Record<K, V> 유틸리티 타입을 사용하면 이를 쉽게 해결할 수 있습니다.

// AS-IS: TypeScript는 map을 { 0.1: string } 타입으로만 추론합니다.
// const map = { 0.1: 'bar' };

// TO-BE: map이 숫자 키와 문자열 값을 갖는 객체임을 명시합니다.
const map: Record<number, string> = {
  0.1: 'bar',
};

const x = Math.random();

// 이제 map[x]는 'any'가 아닌 'string'으로 추론되므로 ESLint 에러가 발생하지 않습니다.
const s: string = map[x];
console.log(s);

2. 인덱스 시그니처 (Index Signature) 사용

Record 타입 대신 인터페이스나 타입 별칭에서 인덱스 시그니처를 직접 정의할 수도 있습니다.

// 숫자형 키를 받고 문자열을 반환하는 인덱스 시그니처 정의
interface NumberMap {
  [key: number]: string;
}

const map: NumberMap = {
  0.1: 'bar',
};

const x = Math.random();
const s: string = map[x]; // 정상 동작

3. VSCode ESLint 서버 재시작 (IDE 오류인 경우)

만약 타입을 올바르게 지정했는데도(혹은 외부 모듈에서 타입을 제대로 가져왔는데도) 계속해서 동일한 에러가 발생한다면, VSCode의 ESLint 서버가 최신 타입 정보를 반영하지 못해 발생한 일시적인 문제일 수 있습니다.

  1. VSCode에서 Cmd + Shift + P (Windows/Linux는 Ctrl + Shift + P)를 눌러 명령 팔레트를 엽니다.
  2. ESLint: Restart ESLint Server를 검색하여 실행합니다.
  3. 에러가 사라지는지 확인합니다.

Deep Dive

위의 해결책으로 ESLint 에러는 해결할 수 있지만, 런타임 안전성이 완벽히 보장되는 것은 아닙니다. Record<number, string>을 사용하면 map[x]의 결과가 항상 string이라고 TypeScript가 믿게 되지만, 실제 런타임에 해당 키가 없다면 undefined가 반환되어 런타임 에러(예: s.toUpperCase() 호출 시)를 유발할 수 있습니다. 더 견고하고 안전한 애플리케이션을 작성하려면 tsconfig.jsoncompilerOptions에서 noUncheckedIndexedAccess 옵션을 true로 설정하는 것이 좋습니다. 이 옵션을 켜면 인덱스 시그니처로 접근한 값이 항상 T | undefined로 추론되므로, 개발자가 옵셔널 체이닝(?.)이나 타입 가드를 통해 명시적으로 undefined에 대한 예외 처리를 하도록 강제할 수 있습니다.

Conclusion

"Unsafe assignment of an error typed value" 에러는 TypeScript가 추론하지 못한 any 타입이 안전하지 않게 변수에 할당되는 것을 막아주는 유용한 방어 기제입니다. 객체를 동적인 맵(Map)이나 딕셔너리처럼 사용할 때는 처음부터 Record 유틸리티 타입이나 인덱스 시그니처를 사용하여 명시적으로 타입을 정의하는 습관을 들이는 것이 좋습니다.

References

협업 및 후원 연락하기 →