← 전체 글로 돌아가기

TypeScript

TypeScript 타입 에러를 any로 덮지 않는 습관

타입 에러가 나면 any로 조용히 덮고 싶은 충동이 생기지만, 그 자리에 정확한 타입을 넣는 게 결국 더 빠르다.

TypeScript를 쓰다 보면 타입 에러가 막막하게 느껴질 때가 있다. 그럴 때 as any: any로 에러를 잠재우는 게 당장은 편하다. 그런데 이렇게 쌓인 any가 나중에 런타임 에러로 돌아오는 경우가 많다.

에러 메시지를 먼저 읽는다

npx tsc --noEmit을 실행하면 에러 전체 목록이 나온다. 에러 메시지에는 어떤 타입이 들어왔고 어떤 타입이 필요한지 적혀 있다.

npx tsc --noEmit

Type 'string | undefined' is not assignable to type 'string' 같은 에러는 any로 덮을 게 아니라, undefined가 들어올 수 있다는 사실을 코드에 반영해야 한다는 신호다.

optional 처리와 타입 가드

가장 흔한 경우는 undefined가 섞인 타입이다.

// 잘못된 방법
const name = (user as any).name;

// 올바른 방법
const name = user?.name ?? '이름 없음';

user?.nameuserundefined이면 undefined를 반환한다. ??null/undefined일 때만 오른쪽 값으로 대체한다.

객체 타입이 여럿 섞인 경우에는 타입 가드를 쓴다.

function isErrorResponse(res: unknown): res is { error: string } {
  return typeof res === 'object' && res !== null && 'error' in res;
}

if (isErrorResponse(data)) {
  console.error(data.error); // 여기서 data.error는 string
}

외부 데이터는 unknown으로 받는다

API 응답이나 JSON.parse 결과처럼 런타임에 들어오는 값은 unknown으로 받는 게 맞다. any와 달리 unknown은 타입을 좁히기 전까지 사용할 수 없어서, 타입 체크를 강제한다.

async function fetchUser(id: string): Promise<User> {
  const res = await fetch(`/api/users/${id}`);
  const data: unknown = await res.json();

  if (!isUser(data)) throw new Error('unexpected shape');
  return data;
}

빌드 에러를 CI에서 잡는다

any가 남아 있어도 빌드는 통과하는 경우가 많다. CI에 tsc --noEmit을 추가해두면 타입 에러가 main에 머지되기 전에 걸린다.

- name: Type check
  run: npx tsc --noEmit

any를 쓰고 싶을 때마다 에러 메시지를 한 번 더 읽어보는 것만으로도 대부분 해결책이 보인다. 타입을 정확히 써두면 그 파일을 나중에 다시 볼 때 맥락을 훨씬 빠르게 파악할 수 있다.