TypeScript: 'Element implicitly has an 'any' type' 동적 객체 키 에러 해결법 (keyof & Record & Index Signature)
- 원인:
string타입 변수로 객체 키에 접근하면 그 변수가 실제로 어떤 키를 담을지 컴파일 시점에 알 수 없어서 발생 - 해결: 키가 고정돼 있으면
as keyof typeof 객체단언 / 키가 계속 늘어나면 인덱스 시그니처([key: string]: Type) 또는Record<string, Type> - 주의: API 응답 JSON을 그대로 순회하며 key로 값을 꺼내 쓸 때 특히 자주 마주치는 에러
const safeKey = key as keyof typeof USER_ROLES;
return USER_ROLES[safeKey];
↓ 왜 이런 에러가 나는지 원리 보기
자바스크립트에서는 문제없이 동작하던 동적 객체 키 접근 코드가 TypeScript로 옮기면 'Element implicitly has an any type' 경고를 띄우는 경우가 많습니다. 이 경고가 발생하는 이유와, 상황별로 쓸 수 있는 해결 방법 세 가지를 정리합니다.
타입스크립트는 왜 동적 키 접근을 에러로 잡는가
타입스크립트의 가장 큰 목적은 컴파일 단계에서 타입 안정성을 확보하는 것입니다. 예를 들어, 객체에 어떤 키가 존재할지 예측 불가능한 상황에서 임의의 변수를 문자열 키로 집어넣어 접근하려고 하면, 타입스크립트는 해당 결과값이 존재하지 않을 수 있어 위험(undefined 가능성)하다고 판단합니다. 그래서 기본값인 any 타입으로 암묵적으로 처리하며 경고를 내뿜게 되는 것입니다. API 응답으로 받은 JSON 객체를 그대로 순회하면서 key로 값을 꺼내 쓸 때 특히 자주 마주치는 에러입니다.
TypeScript의 엄격한 키 매핑 구조
const key: string으로 선언된 변수는 어떤 문자열이든 담길 수 있으므로 객체의 키 목록과 일치하지 않아 차단됨.
'implicitly has an any type' 해결을 위한 3가지 방법
방법 A: keyof와 typeof 연산자를 활용한 타입 단언 (가장 추천)
객체의 키 값이 사전에 정의되어 있는 고정된 구조일 때 가장 우아하게 통과할 수 있는 정석 해결 방법입니다. 변수의 타입을 해당 객체의 키들 중 하나(keyof)로 강제 매칭시킵니다.
const USER_ROLES = {
admin: "관리자",
editor: "편집자",
viewer: "일반사용자"
};
// 에러 발생: key 변수의 string 타입이 USER_ROLES의 키들과 호환되지 않음
function getRoleName(key: string) {
return USER_ROLES[key];
}
// 해결:
function getRoleNameSafe(key: string) {
// key 변수를 USER_ROLES 객체의 키 목록 중 하나로 단언(Assertion)
const safeKey = key as keyof typeof USER_ROLES;
return USER_ROLES[safeKey];
}
방법 B: 인덱스 시그니처(Index Signature) 정의하기
객체의 키 이름이 동적으로 계속 늘어나거나 사전 정의하기 힘들 때 유용한 설계 방식입니다. 객체 타입 자체에 문자열 키가 매핑될 수 있음을 미리 가이드합니다.
// 인덱스 시그니처 타입 선언 [key: string]: string
interface DynamicUser {
name: string;
[key: string]: string; // 임의의 문자열 키 접근 허용
}
const user: DynamicUser = {
name: "김철수",
department: "R&D",
city: "Seoul"
};
function getValue(key: string) {
return user[key]; // 오류 없이 동작
}
방법 C: Record 유틸리티 제네릭 타입 활용하기
타입스크립트가 내장하여 제공하는 Record<Key, Value> 구조를 활용하면 복잡한 인덱스 시그니처 인터페이스 없이도 동적 객체를 빠르게 타이핑할 수 있습니다.
// Key는 string 타입, Value는 number 타입으로 이루어진 동적 맵
const productPrices: Record<string, number> = {
macbook: 2500000,
iphone: 1300000
};
function getPrice(product: string): number {
return productPrices[product] ?? 0; // 타입 체커 통과
}
자바스크립트로 짜여있던 관리자 대시보드를 타입스크립트로 옮기던 중, 설정 화면의 다국어 라벨을 labels[currentLang]처럼 동적 키로 꺼내 쓰는 코드가 유독 많았습니다. 처음엔 코드마다 as any를 붙여서 일단 빌드부터 통과시켰는데, 나중에 다시 보니 어디가 진짜 위험한 접근이고 어디가 안전한 접근인지 구분이 안 됐습니다. 결국 언어 목록처럼 키가 고정된 곳은 keyof typeof 단언으로, 키가 계속 늘어나는 설정값은 Record로 나눠서 정리했습니다.
요약: 동적 객체 접근 문제 해결 맵
키 목록이 고정된 객체를 임의의 문자열 키로 접근할 때는 as keyof typeof 객체 형태로 타입을 단언해 주는 것이 가장 정석적입니다. 키 목록이 수시로 바뀌는 구조라면 [key: string]: Type 인덱스 시그니처를 정의해 두면 됩니다. 단순한 Key-Value 맵이라면 Record<string, Type> 유틸리티 타입을 쓰는 편이 인덱스 시그니처를 매번 선언하는 것보다 간결합니다.
키 목록이 고정돼 있다면 keyof 단언이 가장 안전하고, 키가 계속 늘어나는 구조라면 인덱스 시그니처나 Record가 더 적합합니다. 타입스크립트의 객체 타입 기초가 헷갈린다면 [호마다의 IT 개발 입문 블로그] 도 참고해보세요.
댓글
댓글 쓰기