타입스크립트의 any는 언제 써야할까? 이를 보완할 수 있는 방법은 없을까?
1. JavaScript의 자유로움과 그 한계
JavaScript는 동적 타입(dynamic typing) 언어로, 변수를 선언할 때 특정 타입을 지정할 필요가 없다.
이러한 유연성 덕분에 JavaScript는 빠른 프로토타이핑과 유연한 개발이 가능하지만, 코드의 안정성이 떨어지고, 대규모 프로젝트에서는 유지보수가 어려워지는 문제가 발생한다.
let a = "Hello";
a = 42; // 정상 동작
특히 내가 느꼈던, JavaSciprt의 한계점은 크게 두가지였다.
- 타입 오류가 런타임에서야 발견됨 → 해당 타입만 가지고있는 메서드 사용하다가, API 콜 타입이 달라지면 그대로 에러.
- 협업 시 코드의 의도가 불명확 → 유지보수와 확장성이 어려워짐. 오타 포함. 사실 오타때문에 더 고생하는 것 같았음.
2. TypeScript의 등장과 any의 필요성
이러한 문제를 해결하기 위해 2012년 Microsoft는 JavaScript에 정적 타입을 추가한 TypeScript를 발표했다. TypeScript는 JavaScript의 자유로운 특성을 유지하면서도 타입 안정성을 제공하는 것이 목표였다. 하지만 문제는 JavaScript 코드가 이미 너무 널리 퍼져 있었다는 점이었다.
- 기존 JavaScript 코드를 TypeScript로 변환하려면 모든 변수에 타입을 추가해야 했는데, 이는 현실적으로 어려웠음
(필자는 작은 사이즈에서 마이그레이션하다가 눈 빠질 뻔했음. 그나마 AI 덕분에 비교적 빠르게 마이그레이션함) - JavaScript의 기존 라이브러리들이 정적 타입을 지원하지 않았음.(라이브러리 운용자체는 안정성 있는데, 3년전 업데이트..가 마지막 이라던지)
그래서 점진적 전환(Migration)이 필요했고, JavaScript의 동적 특성을 유지하면서도 정적 타입 시스템을 제공하기 위함으로 생각된다.
2. any 타입의 사용해야 하는 경우
- 타입 정보를 제공하지 않는 외부 라이브러리 사용 시: 타입 선언이 없는 서드파티 라이브러리를 사용할 때, 해당 라이브러리의 반환 값이나 객체를 any로 처리하여 컴파일러 오류를 방지할 수 있다. 그러나 이는 임시 방편이며, 타입을 제공하는 유사한 라이브러리로 마이그레이션하는 거싱 낫다고 생각이 든다.
- 점진적인 타입 적용 과정에서: 기존의 방대한 JavaScript 코드를 TypeScript로 마이그레이션할 때, 모든 코드를 한 번에 타입 지정하기 어려울 수 있다. 시간이 없다는 전제 조건 하에 사용하는게 좋을 것 같다.
- 응답 타입을 알 수 없음
3. any의 문제점
하지만 any는 TypeScript의 타입 안전성을 해칠 수 있다.
2021년 axios에서 작은 논쟁(?)이 일어난 글을 찾았는데 꽤 흥미롭다.
https://github.com/axios/axios/issues/4141
Revert #3002 - set type of data to any · Issue #4141 · axios/axios
Is your feature request related to a problem? Please describe. Yes. After #3002 was merged, we are now required to set explicit type definitions for every single axios request we make, even if we d...
github.com
- any를 사용하면 컴파일러의 타입 검사를 우회할 수 있다.
- 타입 검사 무력화: any를 사용하면 TypeScript의 타입 체크 기능이 제대로 동작하지 않음.
- 디버깅 어려움: 타입 오류가 컴파일 시점이 아니라 런타임에서야 발견됨.
- any를 남발하면 결국 JavaScript와 다를 바 없어짐.
- 따라서 최근에는 unknown, strict mode, noImplicitAny, 더 정교하게 타입을 추론하는 등의 기능을 활용하여 any의 사용을 최소화하는 방향으로 발전하고 있다.
예시1) unknown을 사용하면 any와 유사하지만, 반드시 타입 검사를 거쳐야한다.
let value: unknown = getValue();
if (typeof value === "string") {
console.log(value.toUpperCase()); // 타입이 확인된 후 사용 가능
}
예시2) noImplicitAny를 활성화하면 위와 같이 매개변수 a와 b에 타입이 명시되지 않았을 때 오류가 발생한다.
function add(a, b) {
return a + b; // Error: Parameter 'a' implicitly has an 'any' type.
}
예시3) 유니온 타입과 인터섹션 타입
function getValue(value: string | number) {
console.log(value);
}
예시4) 타입가드
function checkType(value: string | number) {
if (typeof value === "string") {
console.log(value.toUpperCase()); // 타입이 'string'으로 좁혀진다.
} else {
console.log(value.toFixed(2)); // 타입이 'number'로 좁혀진다
}
}
예시5) 제네릭을 이용한 타입추론
사용자가 제네릭을 통해 타입을 명시적으로 지정할 수 있도록 유도한다.
function test<T>(arg: T): T {
return arg;
}
const result = test(5); // TypeScript는 'result'를 'number'로 추론된다.
예시 6) 타입 리터럴 (개인적으로 좀 더 좁힐 수 있는 강력한 방법이라고 생각된다)
TypeScript 3.4부터는 타입 리터럴을 사용하여 문자열이나 숫자 값의 정확한 값을 타입으로 지정할 수 있디.
type Direction = "up" | "down" | "left" | "right";
const dir: Direction = "up"; // 이 값은 'up', 'down', 'left', 'right' 중 하나여야 한다.
4. 그럼에도 불구하고...
TypeScript가 발전하면서 any를 사용할 필요가 점점 줄어들고 있지만, 완전히 제거되지는 않았다.
그 이유는 TypeScript가 유연성과 타입 안전성 사이의 균형을 유지해야 하기 때문이라고 생각한다.
- 초보 개발자나 JavaScript에서 넘어온 개발자들이 쉽게 적응할 수 있도록
- 빠른 프로토타이핑과 점진적인 타입 적용을 지원하기 위해
- 레거시 JavaScript 코드와의 호환성을 유지하기 위해
즉, any는 필요하지만 조심해야 하는 도구로 남아있는게 아닐까싶다.
출처:
https://www.typescriptlang.org/ko/docs/handbook/2/everyday-types.html?utm_source=chatgpt.com
Documentation - Everyday Types
언어의 원시 타입들.
www.typescriptlang.org
https://www.typescriptlang.org/docs/handbook/intro.html