React 프로젝트에 TypeScript 점진적으로 도입하기

배경

1년 반 동안 운영한 React 프로젝트에 TypeScript를 도입하기로 했다. 컴포넌트가 200개가 넘는 상황에서 한 번에 전환하는 것은 불가능했고, 점진적 마이그레이션 전략이 필요했다.

마이그레이션 전략

1. tsconfig.json 설정

{
  "compilerOptions": {
    "target": "es5",
    "lib": ["dom", "es2015"],
    "allowJs": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "strict": false,
    "forceConsistentCasingInFileNames": true,
    "module": "esnext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "react"
  },
  "include": ["src"]
}

핵심은 allowJs: truestrict: false였다. 기존 JS 파일과 새로운 TS 파일이 공존할 수 있게 했고, 엄격한 타입 체크는 나중으로 미뤘다.

2. 우선순위 설정

하위 컴포넌트부터 상위로 올라가는 Bottom-up 방식을 선택했다. 의존성이 적은 유틸 함수와 공통 컴포넌트를 먼저 전환했다.

// utils/format.ts
export const formatCurrency = (amount: number): string => {
  return new Intl.NumberFormat('ko-KR', {
    style: 'currency',
    currency: 'KRW'
  }).format(amount);
};

// components/Button.tsx
interface ButtonProps {
  onClick: () => void;
  children: React.ReactNode;
  variant?: 'primary' | 'secondary';
}

const Button: React.FC<ButtonProps> = ({ onClick, children, variant = 'primary' }) => {
  return (
    <button className={`btn btn-${variant}`} onClick={onClick}>
      {children}
    </button>
  );
};

3. 타입 정의 파일 활용

서드파티 라이브러리 타입은 @types 패키지로 설치했다. 타입 정의가 없는 라이브러리는 .d.ts 파일을 직접 작성했다.

// types/custom.d.ts
declare module 'legacy-library' {
  export function doSomething(param: string): void;
}

실제 효과

한 달간 주요 컴포넌트 50개를 전환했다. Props 타입 오류를 10건 이상 사전에 발견했고, 리팩토링 시 IDE 자동완성으로 생산성이 체감상 20% 정도 올랐다.

교훈

처음부터 strict: true로 시작했다면 팀원들의 거부감이 컸을 것이다. 점진적 전환과 느슨한 설정으로 시작해 서서히 엄격하게 가져가는 전략이 효과적이었다.

React 프로젝트에 TypeScript 점진적으로 도입하기