본문 바로가기
React

[Typescript] any 타입 대체 방법

by jyee 2025. 3. 2.
728x90
반응형

 

처음에 실무 시작하게 되면서 TypeScript를 접했을 때 타입 정의가 어려웠다. 일단 익숙하지 않았던 것도 있었고 그냥 any를 써도 넘어 가길래 애매하다 싶으면 타입을 any로 지정하고 자꾸 쓰게 되었는데...

 

하지만 any를 마구잡이로 사용하면 생기는 문제

1. 타입 체크가 무의미해짐 

any를 사용하면 TypeScript의 장점인 자동완성과 타입 체크를 포기하는 셈이다.

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

const data = await fetchData();
console.log(data.title.toUpperCase()); // ❌ 런타임 에러 가능!

위 예시로 API에서 데이터를 가져올 때 fetchData의 반환타입이 any이기 때문에 title이 실제로 존재하는지 알 수 없고 title이 undefined일 수도 있는데 .toUpperCase()를 호출하면 런타임 에러가 발생한다. 

IDE의 자동완성이 작동되지 않는다! data. 까지 입력하면 가능한 속성이 추천되는데 any이기때문에 자동완성이 안됨

 

2. 디버깅 어려워짐 

물론 any를 쓰면 타입을 몰라도 개발이 가능하기에 처음에는 빠르게 작업이 가능하다.그러나 문제가 발생하면 디버깅 시간이 길어진다. 어떤 API응답이 오는지 알 수가 없어서 콘솔로 일일이 찍어보면서 확인해야 했다. 이 과정이 반복되면 디버깅 시간이 증가하는 문제가 있었다.

 

3. 협업에서도 문제

결국 내 코드의 any를 다 지워가면서 했는데 나중에 보니 동료도 any를 남발했던 것이다..! 그 동료 역시 typescript 처음 접하는 거라서 any를 더 편하게 썼다고 했다. any를 사용하면 타입이 보장되지 않으니 추후 유지보수와 확장성 측면을 위해 any는 최대한 다 지우자라고 얘기해서 하나씩 수정해나갔다. 오히려 이렇게 다시 수정하는 시간이 오래걸린 것 같았다.  


any 대체하는 방법

   1. 정확한 타입 정의

interface Post {
  id: number;
  title: string;
  content: string;
}

const fetchData = async (): Promise<Post[]> => {
  const response = await fetch("https://api.example.com/data");
  return await response.json();
};

const posts = await fetchData();
console.log(posts[0].title); // 자동완성 & 타입 체크 가능!

 

   2. unknown을 활용하기 

unknowm타입은 이름처럼 무엇이 할당될지 아직 모르는 상태의 타입을 말한다. 

unknown은 any처럼 어떤 타입이든 저장할 수 있지만, 무조건 타입검사를 해야한다는 제약이 있다.

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

const data = await fetchData();

if (typeof data === "object" && data !== null && "title" in data) {
  console.log((data as { title: string }).title);
}

 

📌 any 타입과 unknown 타입의 차이점

크게 두가지로 요약할 수 있는데 

1. unknown 타입은 any를 제외한 다른 타입에 할당할 수 없다.

2. unknown 타입은 메서드호출, 인스턴스 생성, 프로퍼니 접근을 할 수 없다(타입 좁히기는 가능) 

 

 

   3. Generic 활용하기 

Generic 사용하면 any 대신 필요한 타입을 호출하는 순간 정할 수 있는 유연한 코드를 만들 수 있다.

아래 예시로

  • wrap<T>를 사용하면, T가 호출할 때 자동으로 타입을 추론한다.
  • "hello"를 넘기면 T는 string으로 인식되므로, wrappedString.value가 string임을 확신할 수 있다.
  • 따라서 자동완성도 정상 작동하고, 타입 체크도 제대로 된다.

 

function wrap<T>(value: T): { value: T } {
  return { value };
}

const wrappedString = wrap("hello");
console.log(wrappedString.value.toUpperCase()); //자동완성 & 타입 체크 가능!

 

 

   4. TypeScript의 Record타입 활용

 객체의 키(key)와 값(value)의 타입을 미리 지정하는 방법이다. Record<k,v>를 사용하면 보다 타입 안전한 코드를 작성할 수 있다.

const userRoles: Record<"admin" | "user" | "guest", string> = {
  admin: "관리자",
  user: "일반 사용자",
  guest: "손님",
};

console.log(userRoles.admin.toUpperCase()); // ✅ 정상 동작
console.log(userRoles.unknownKey); // ❌ 컴파일 오류 발생!

 

 

위 예시처럼

  • admin, user, guest 외의 키를 입력하면 컴파일 단계에서 오류 발생한다
  • console.log(userRoles.unknownKey);는 unknownKey가 없기 때문에 TypeScript가 미리 경고해준다
  • userRoles.까지 입력하면 자동으로 admin, user, guest 추천함

 

 

TypeScript에서 any를 남발하는 것은 처음에는 편할 수 있지만, 그저 개발만 빠르게 하면 된다는 나의 편협한 생각이였다.  장기적으로는 코드의 안정성, 유지보수성, 그리고 협업 효율성에 문제가 생길 수 있으며  any 대신 정확한 타입 정의, unknown, Generic, **Record**와 같은 TypeScript의 다양한 타입 시스템을 활용하면 훨씬 더 안전하고 견고한 코드를 작성할 수 있다.

728x90
반응형