Typescript any 타입 지양하기
- 타입스트립트의 타입 시스템은 점진적이고 선택적입니다. 코드에 타입을 조금씩 추가할 수 있기 때문에 점진적이며, 언제든지 타입 체커를 해제할 수 있기 때문에 선택적입니다. 이 기능들의 핵심은 any 타입입니다.
let age: number;
age = "12"; // "string" 형식은 "number" 형식에 할당할 수 없습니다.
age = "12" as any; // OK
- 위 코드에서 타입 체커를 통해 오류를 찾아냈습니다. 오류는 as any를 추가해 해결할 수 있습니다. 그러나 일부 특별한 경우를 제외하고는 any를 사용하면 타입스크립트의 수많은 장점을 누릴 수 없게 됩니다. 부득이하게 any를 사용하더라도 그 위험성을 알고 있어야 합니다.
- 이번 post에서는 any를 지양해야 하는 이유에 대해 알아보겠습니다.
any 타입에는 타입 안정성이 없습니다.
- 앞선 예제에서 age는 number 타입으로 선언되었습니다. 그러나 as any를 사용하면 string 타입을 할당할 수 있게 됩니다. 타입 체커는 선언에 따라 number 타입으로 판단할 것이고 이는 프로젝트가 커지거나, 디버깅에서 혼돈을 불러올 것 입니다.
age += 1; // 런타임에 정상, age는 "121"
any는 함수 시그니처를 무시해 버립니다.
- 함수를 작성할 때는 시그니처를 명시해야 합니다. 호출하는 쪽은 약속된 타입의 입력을 제공하고, 함수는 약속된 타입의 출력을 반환합니다. 그러나 any타입을 사용하면 이런 약속을 어길 수 있습니다.
function calculateAge (birthDate: Date) {
// ...
}
let birthDate: any = "2024-09-02";
calculateAge(birthDate)
- birthDate 매개변수는 string이 아닌 Date 타입이어야 합니다. any 타입을 사용하면 calculateAge의 시그니처를 무시하게 됩니다. 자바스크립트에서는 종종 암시적으로 타입이 변환되기 때문에 이런 경우 특히 문제가 될 수 있습니다. string 타입은 number 타입이 필요한 곳에서 오류 없이 실행될 때가 있고, 그럴 경우 다른곳에서 문제를 일으킬 수 있습니다.
any 타입에는 언어 서비스가 적용되지 않습니다.
- 어떤 심벌에 타입이 있다면 타입스크립트 언어 서비스는 자동완성 기능과 적절한 도움말을 제공합니다.

- 그러나 any 타입인 심벌을 사용하면 아무런 도움도 받지 못합니다.

any 타입은 코드 리펙터링 때 버그를 감춥니다.
- 어떤 아이템을 선택할 수 있는 웹 어플리케이션을 만든다고 가정해 보겠습니다. 어플리케이션에는 onSelectItem 콜백이 있는 컴포넌트가 있을 겁니다. 선택하려는 아이템의 타입이 무엇인지 알기 어려우니 any를 사용해 보겠습니다.
interface ComponentProps {
onSelectItem: (item: any) => void;
}
function renderSelector (props: ComponentProps) {}
let selectedId: number = 0;
function handleSelectItem(item: any) {
selectedId = item.id;
}
renderSelector({onSelectItem: handleSelectItem})
- onSelectItem에 아이템 객체를 필요한 부분만 전달하도록 컴포넌트를 개선해 보겠습니다. 여기서는 id만 필요합니다.
interface ComponentProps {
onSelectItem: (id: number) => void;
}
- 컴포넌트를 수정하고, 타입 체크를 모두 통과했습니다.
- 타입 체크를 통과했다고 끝난 것이 아닙니다. handleSelectItem은 any 매개변수를 받습니다. 따라서 id를 전달받아도 문제가 없다고 나옵니다. id를 전달 받으면, 타입 체커를 통과함에도 불구하고 런타임에는 오류가 발생할 겁니다.
- any가 아니라 구체적인 타입을 사용했다면, 타입 체커가 오류를 발견했을 겁니다.
- 오류의 원인: handleSelectItem 함수는 여전히 item 객체를 기대하고 있지만, 이제 컴포넌트는 id만을 전달합니다.
- 런타임 오류
function handleSelectItem(item: any) {
selectedId = item.id;
}
- 이 함수가 실행될 때, item은 더 이상 객체가 아니라 숫자(number)입니다. 따라서 item.id를 접근하려고 할 때 다음과 같은 오류가 발생합니다:
TypeError: Cannot read property 'id' of undefined
TypeError: item.id is not a property
- 오류의 결과: selectedId가 제대로 업데이트되지 않습니다.
- 디버깅의 어려움: any 타입으로 인해 타입 체커가 이 문제를 감지하지 못했기 때문에, 개발자가 런타임에 이 오류를 발견하기 전까지는 문제를 인식하기 어렵습니다. 구체적인 타입을 사용했다면, 컴파일 시점에 이 불일치를 발견하고 수정할 수 있었을 것입니다.
any는 타입 설계를 감춰버립니다.
- 어플리케이션 상태 같은 객체를 정의하려면 꽤 복잡합니다. 상태 객체 안에 있는 수많은 속성의 타입을 일일이 작성해야 하는데, any 타입을 사용하면 간단히 끝내버릴 수 있습니다.
- 물론 이때도 any를 사용하면 안 됩니다. 이유는 앞에서 설명했듯이, 상태 객체의 설계를 감춰버리기 때문입니다. 깔끔하고 정확하고 명로한 코드 작성을 위해 제대로 된 타입 설계는 필수입니다. any 타입을 사용하면 타입 설계가 불분명해집니다. 설계가 잘 되었는지, 설계가 어떻게 되어 있는지 전혀 알 수 없습니다.
- 만약 동료가 코드를 검토해야 한다면, 동류는 어플리케이션의 상태를 어떻게 변경했는지 처음부터 재구성해 봐야 할 겁니다. 그러므로 설계가 명확이 보이도록 타입을 일일이 작성하는 것이 좋습니다.
any는 타입시스템의 신뢰도를 떨어뜨립니다.
- 사람은 항상 실수를 합니다. 보통은 타입 체커가 실수를 잡아주고 코드의 신뢰도가 높아집니다. 그러나 런터임에 타입 오류를 발견하게 된다면 타입 체커를 신뢰할 수 없을 겁니다. 대규모 팀에 타입스크립트를 도입하려는 상황에서, 타입 체커를 신뢰할 수 없다면 큰 문제가 될 겁니다. any 타입을 쓰지 않으면 런터임에 발견될 오류를 미리잡을 수 있고 신뢰도를 높일 수 있습니다.
결론
- any 타입을 사용하면 타입 체커와 타입스크립트 언어 서비스를 무력화시켜 버립니다. any 타입은 진짜 문제점을 감추며, 개발 경험을 나쁘게 하고, 타입 시스템의 신뢰도를 떨어뜨립니다. 최대한 사용을 피하도록 합시다.
참고: 이펙티브 타입스크립트