vanillinav 2024. 10. 9. 18:50
728x90
반응형

타입스크립트에서 any 타입은 모든 타입을 포함하는 가장 상위 타입입니다. any 타입 변수에는 어떤 값이든 할당할 수 있지만, 이는 타입스크립트의 정적 타입 검사 기능을 해제하는 효과가 있으므로 신중하게 사용해야 합니다. any 타입의 사용은 컴파일 시점의 타입 검사를 포기하는 것과 같으므로, 가능하면 다른 타입을 사용하는 것이 좋습니다. 이 글에서는 any 타입의 정의, 사용 예시, 대안, 그리고 any 타입을 사용해야 하는 제한적인 상황을 설명하고, 각 항목에 예제 코드를 포함합니다.

1. any 타입의 정의

any 타입은 타입스크립트 컴파일러에게 "이 변수의 타입을 검사하지 마세요"라고 명시적으로 지시합니다. 따라서 any 타입 변수에는 어떤 값도 할당 가능하며, 값에 접근할 때도 타입 검사가 이루어지지 않습니다. 이는 런타임 오류의 가능성을 높이므로, 신중한 사용이 필수적입니다. any 타입은 자바스크립트의 var 키워드와 유사한 동작을 합니다.

let state: any;

state = { value: 0 }; // 객체를 할당해도
state = 100; // 숫자를 할당해도
state = "hello world"; // 문자열을 할당해도
state.foo.bar = () => console.log("this is any type"); // 심지어 중첩 구조로 들어가 함수를 할당해도 문제없다

책에서는 FeedbackModalParams라는 타입을 정의하고 있습니다. 이 타입은 모달(Modal)이 열릴 때 필요한 여러 가지 매개변수를 정의한 것으로 복잡한 객체의 구조를 안전하게 정의하는 방법을 보여줍니다.

type FeedbackModalParams = {
    show: boolean;
    content: string;
    cancelButtonText?: string;
    confirmButtonText?: string;
    beforeOnClose?: () => void;
    action?: any;
};

2. any 타입 사용 예시 및 문제점

다음 예시는 any 타입의 사용법과 그로 인해 발생할 수 있는 문제점을 보여줍니다.

let value: any = 10;          // 숫자 값 할당
value = "hello";             // 문자열 값 할당
value.toUpperCase();         // 괜찮을 수도 있고, 에러가 날 수도 있음 (런타임 에러 가능성)

value = { name: "John", age: 30 };
console.log(value.age);     // 정상 작동

value = [1, 2, 3];
console.log(value.length); // 정상 작동

value = true;
console.log(value.length); // 런타임 에러 발생!  boolean 타입에는 length 프로퍼티가 없음

위 예시에서 value 변수는 any 타입이므로, 어떤 타입의 값도 할당할 수 있습니다. 하지만 value에 어떤 타입의 값이 할당되어 있는지 알 수 없기 때문에, value.toUpperCase()value.length 와 같은 접근은 컴파일 시점에는 에러가 발생하지 않지만, 런타임 시에 예상치 못한 에러를 발생시킬 수 있습니다. 이러한 런타임 에러는 디버깅을 어렵게 만들고, 프로그램의 안정성을 저해합니다.

async function load() {
  const response = await fetch("https://api.com");
  const data = await response.json(); // response.json()의 리턴 타입은 Promise<any>로 정의되어 있다
  return data;
}

Typescript에서 Promise는 비동기 작업의 결과가 어떤 타입이든 반환될 수 있음을 의미합니다. 이를 통해 API 호출이나 비동기 작업의 반환 값이 미리 확정되지 않은 상황에서 일단 모든 타입을 허용하는 장점을 제공합니다. 그러나 Promise를 사용하면 타입 안정성(Type safety)을 잃어버릴 수 있습니다. Typescript의 주요 장점 중 하나인 타입 추론오류 검출을 충분히 활용하지 못하게 됩니다. 따라서 개발 후반이나 API의 응답 구조가 명확해지면, 구체적인 타입을 지정하는 것이 좋습니다.

type ApiResponse = {
  id: number;
  name: string;
  email: string;
};

async function load(): Promise<ApiResponse> {
  const response = await fetch("https://api.com");
  const data: ApiResponse = await response.json();
  return data;
}

위 예시에서 ApiResponse 타입을 미리 정의하고, 이를 통해 비동기 함수의 반환값을 명시적으로 지정했습니다. 이는 코드의 안정성을 높여주고, 개발 중간에 발생할 수 있는 타입 오류를 미연에 방지할 수 있습니다.

3. any 타입을 사용해야 하는 제한적인 상황 (그리고 대안)

any 타입은 가능한 한 피해야 하지만, 다음과 같은 상황에서는 제한적으로 사용될 수 있습니다. 하지만 가능한 대안을 함께 제시합니다.

  • 외부 라이브러리와의 상호 작용 (대안: 타입 정의 파일 또는 unknown): 타입 정의 파일(.d.ts)이 없는 외부 라이브러리를 사용할 때, 해당 라이브러리의 객체나 함수의 타입을 any로 지정할 수 있습니다. 하지만 unknown 타입을 사용하여 런타임 타입 체크를 수행하는 것이 더 안전합니다.(타입 정의 파일을 찾아 사용하는 것이 가장 좋습니다. DefinitelyTyped (https://github.com/DefinitelyTyped/DefinitelyTyped) 에서 검색해 볼 수 있습니다.)
// 타입 정의가 없는 외부 라이브러리 함수 (예시)
declare const someExternalLibraryFunction: any;

// any 타입을 사용하여 함수 호출
const result = someExternalLibraryFunction(10, "hello");

// result의 타입은 알 수 없으므로,  사용 전에 타입 검사가 필요합니다.
if (typeof result === 'number') {
  console.log("Result is a number:", result);
} else if (typeof result === 'string') {
  console.log("Result is a string:", result);
} else {
  console.log("Result is of unknown type:", result);
}

// 더 나은 방법:  unknown 타입 사용
declare const someExternalLibraryFunctionUnknown: (a: number, b: string) => unknown;
const resultUnknown = someExternalLibraryFunctionUnknown(10, "hello");

if (typeof resultUnknown === 'number') {
    const numResult = resultUnknown as number; // 타입 단언
    console.log("Number result:", numResult);
} else if (typeof resultUnknown === 'string'){
    const strResult = resultUnknown as string; // 타입 단언
    console.log("String result:", strResult);
} else {
    console.log("Unknown type result:", resultUnknown);
}
  • 레거시 코드와의 통합 (대안: 점진적 타입 추가): 타입이 없는 기존 자바스크립트 코드와 통합할 때, 일부 변수를 any로 처리할 수 있습니다. 하지만 장기적으로는 점진적으로 타입을 추가하는 리팩토링을 진행해야 합니다.
// 타입이 없는 레거시 함수 (예시)
function getLegacyData(): any {
  return { id: 1, name: "John Doe" };
}

// any 타입을 사용하여 데이터 처리
let legacyData: any = getLegacyData();
console.log(legacyData.id); // 작동하지만, 타입 안전하지 않음

// 더 나은 방법: 인터페이스를 정의하고 타입 단언 사용
interface LegacyDataType {
  id: number;
  name: string;
}

let legacyDataTyped: LegacyDataType = getLegacyData() as LegacyDataType; // 타입 단언 (주의:  런타임 에러 가능성 존재)
console.log(legacyDataTyped.id); // 타입 안전성을 높이지만, 여전히 런타임 에러 가능성 존재
  • 단기적인 프로토타이핑 (대안: unknown 또는 인터페이스): 빠른 프로토타이핑 단계에서 타입에 대한 고민 없이 코드를 작성해야 할 경우, any 타입을 사용할 수 있습니다. 하지만 프로젝트가 완성 단계에 가까워지면 any 타입을 제거하고 적절한 타입으로 변경해야 합니다. unknown을 사용하거나, 필요에 따라 인터페이스를 정의하는 것이 더 나은 선택입니다.

4. any 타입의 대안

any 타입 대신 다음과 같은 타입을 사용하는 것이 좋습니다.

  • unknown 타입: any와 유사하지만, unknown 타입의 변수는 값을 사용하기 전에 타입 검사를 거쳐야 합니다. any보다 훨씬 안전한 대안입니다. (위 예제 참조)
  • object 타입: null 또는 undefined가 아닌 객체를 나타냅니다. any보다는 좀 더 제한적인 타입 검사를 제공합니다. 하지만 모든 객체를 포함하므로, 특정 객체의 구조를 명확히 알고 싶다면 인터페이스를 사용하는 것이 더 좋습니다.
let obj: object = { name: "John" };
// obj.name; // 에러 발생!  object 타입은 name 프로퍼티를 보장하지 않음.
  • 제네릭: 다양한 타입을 처리할 수 있는 유연성을 제공하면서 타입 안전성을 유지할 수 있습니다.
function identity<T>(arg: T): T {
  return arg;
}
let myString: string = identity<string>("hello");
let myNumber: number = identity<number>(10);
  • 인터페이스 또는 타입 별칭: 객체의 구조를 명확하게 정의하여 타입 안전성을 확보할 수 있습니다.
interface Person {
  name: string;
  age: number;
}

let person: Person = { name: "Jane", age: 25 };

5. 결론

`any` 타입은 편리함을 제공하지만, 타입 안전성을 훼손할 위험이 있으므로 신중하게 사용해야 합니다. 타입스크립트의 강력한 기능인 정적 타입 검사를 무력화시키기 때문에, 컴파일 시점에 발견되지 않는 런타임 에러의 가능성을 높입니다. 따라서 가능하면 `unknown`, 제네릭, 인터페이스, 타입 별칭 등의 대안을 활용하여 타입스크립트의 장점을 최대한 누리는 것이 좋습니다.

 

`any` 타입은 정말 어쩔 수 없는 상황, 예를 들어 타입 정보가 전혀 없는 레거시 코드와의 통합이나, 타입 정의 파일이 없는 외부 라이브러리와의 상호 작용 등에 최후의 수단으로 사용해야 합니다. 그리고 이러한 경우에도, `any` 타입의 사용 범위를 최소화하고, 장기적인 관점에서 `any` 타입을 점진적으로 제거하고 보다 안전한 타입으로 대체하는 리팩토링 계획을 세우는 것이 중요합니다.`any` 타입은 코딩 속도를 높이고 코드 작성을 간편하게 해주는 매력적인 선택지지만, 타입스크립트가 제공하는 강력한 정적 타입 검사의 이점을 포기하게 만드는 위험한 선택이기도 합니다.  `any` 타입을 사용하면 컴파일러가 타입 검사를 건너뛰기 때문에, 런타임까지 발견되지 않는 예상치 못한 오류가 발생할 가능성이 높아집니다.  이는 디버깅 시간을 늘리고, 심각한 버그로 이어질 수 있습니다.

따라서 `any` 타입은 정말 어쩔 수 없는 상황에만 최후의 수단으로 사용해야 합니다. 이러한 노력을 통해 타입스크립트의 장점을 최대한 활용하고, 보다 안전하고 유지보수가 용이한 코드베이스를 구축할 수 있습니다.  결론적으로, `any` 타입은 편리함과 안전성 사이에서 신중한 선택을 요구하는 양날의 검과 같습니다.  그 편리함에 혹하지 말고,  안전성을 우선시하는 개발 문화를 정착시켜야 합니다.

 

6. 참조

- 우아한 타입스크립트 with 리액트: 3.1 타입스크립트만의 독자적인 타입 시스템

- 타입스크립트 핸드북: https://www.typescriptlang.org/docs/handbook/intro.html

728x90
반응형