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?.name은 user가 undefined이면 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를 쓰고 싶을 때마다 에러 메시지를 한 번 더 읽어보는 것만으로도 대부분 해결책이 보인다. 타입을 정확히 써두면 그 파일을 나중에 다시 볼 때 맥락을 훨씬 빠르게 파악할 수 있다.