JavaScript 프로젝트에 TypeScript 점진적으로 도입하기
배경
팀 내 JavaScript 코드베이스가 2만 라인을 넘어가면서 런타임 에러가 잦아졌다. 특히 API 응답 구조가 바뀌거나 함수 인자를 잘못 전달하는 경우가 많았다. TypeScript 도입을 검토했지만, 전체를 한 번에 전환하기엔 일정상 무리였다.
마이그레이션 전략
tsconfig.json에서 allowJs: true와 checkJs: false로 시작했다. JS와 TS 파일을 혼용할 수 있게 하되, 기존 JS는 체크하지 않는 방식이다.
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"lib": ["es2017", "dom"],
"allowJs": true,
"checkJs": false,
"jsx": "react",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true
}
}
우선순위는 다음과 같이 정했다:
- 유틸 함수와 공통 모듈
- API 클라이언트와 타입 정의
- 비즈니스 로직이 복잡한 컴포넌트
- 나머지 컴포넌트
실제 적용 사례
API 응답 타입을 먼저 정의했다.
// types/api.ts
export interface User {
id: number;
name: string;
email: string;
role: 'admin' | 'user';
}
export interface ApiResponse<T> {
data: T;
status: number;
message?: string;
}
API 클라이언트를 TS로 전환하면서 타입 안정성을 확보했다.
// api/user.ts
import { User, ApiResponse } from '../types/api';
export async function fetchUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
const json: ApiResponse<User> = await response.json();
return json.data;
}
마주친 문제들
외부 라이브러리 타입 정의가 없는 경우가 많았다. @types/ 패키지를 찾거나, 없으면 직접 .d.ts 파일을 작성해야 했다.
// types/custom.d.ts
declare module 'legacy-library' {
export function doSomething(value: string): void;
}
JS 파일에서 TS 파일을 import할 때 타입 체크가 안 되는 점도 아쉬웠다. 결국 중요 파일부터 먼저 변환하는 수밖에 없었다.
결과
3개월간 약 40%를 TS로 전환했다. 타입 에러를 컴파일 타임에 잡으면서 프로덕션 버그가 체감상 30% 정도 줄었다. IDE 자동완성도 정확해져서 개발 속도도 빨라졌다.
완전한 전환까지는 시간이 더 걸리겠지만, 점진적 접근이 현실적인 선택이었다.