TypeScript 마이그레이션 중 any 타입과의 전쟁
문제 상황
회사 메인 프로젝트를 TypeScript로 전환하기로 결정했다. 3년간 운영된 React 프로젝트였고, 코드베이스가 상당했다. 일단 .js 파일을 .tsx로 바꾸고 any를 붙여가며 빌드부터 통과시켰는데, 이게 문제의 시작이었다.
// 초기 마이그레이션 코드
function fetchUserData(id: any): any {
return api.get(`/users/${id}`).then((res: any) => res.data);
}
TypeScript를 도입한 의미가 없었다. any 투성이라 런타임 에러는 여전했다.
해결 과정
1. tsconfig.json 설정 조정
처음엔 strict: false로 시작했다가, 단계적으로 옵션을 켜기로 했다.
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": false,
"strictFunctionTypes": false
}
}
2. API 응답 타입 정의
가장 먼저 외부 API 응답부터 타입을 정의했다. Swagger 문서를 참고해서 인터페이스를 작성했다.
interface User {
id: number;
name: string;
email: string;
createdAt: string;
}
function fetchUserData(id: number): Promise<User> {
return api.get(`/users/${id}`).then(res => res.data);
}
3. Utility Types 활용
중복 타입 정의를 줄이기 위해 TypeScript 내장 유틸리티 타입을 적극 활용했다.
type UserFormData = Pick<User, 'name' | 'email'>;
type PartialUser = Partial<User>;
교훈
- 마이그레이션은 빌드 통과가 목표가 아니라 타입 안정성 확보가 목표다
- strict 모드를 처음부터 켜는 게 장기적으론 이득이다
- API 경계부터 타입을 정의하면 내부로 전파하기 쉽다
여전히 일부 복잡한 고차 컴포넌트에는 any가 남아있지만, 핵심 도메인 로직은 타입 커버리지가 80%를 넘었다. 다음 분기엔 strictNullChecks를 켤 예정이다.