프로젝트에 TypeScript 3.5 도입하며 겪은 것들

배경

2017년부터 운영 중인 React 프로젝트의 런타임 에러가 잦아졌다. PropTypes로는 한계가 명확했고, 리팩토링 시 사이드 이펙트를 예측하기 어려웠다. TypeScript 도입을 제안했고, 3주간의 검증 기간을 거쳐 승인받았다.

마이그레이션 전략

한 번에 전환하는 것은 리스크가 컸다. .js.ts를 공존시키는 방식으로 진행했다.

// tsconfig.json
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": false,
    "strict": false,
    "target": "es5",
    "module": "esnext",
    "jsx": "react"
  }
}

초기에는 strict: false로 시작했다. 기존 코드를 건드리지 않으면서 새 코드만 .tsx로 작성하는 방식이었다.

주요 이슈

1. Redux 타입 정의

가장 골치 아팠던 부분이다. connect의 타입 추론이 제대로 되지 않아 any를 남발하게 됐다.

// 초기 시도 - 타입이 제대로 추론되지 않음
const mapStateToProps = (state: any) => ({
  user: state.user
});

export default connect(mapStateToProps)(Component);

결국 ReturnType을 활용한 패턴으로 정리했다.

const mapStateToProps = (state: RootState) => ({
  user: state.user
});

type StateProps = ReturnType<typeof mapStateToProps>;

const Component: React.FC<StateProps> = ({ user }) => {
  // ...
};

2. Third-party 라이브러리 타입

@types/ 패키지가 없는 라이브러리들이 많았다. declare module로 임시 대응했다.

// src/types/custom.d.ts
declare module 'react-some-library' {
  export const SomeComponent: any;
}

3. Webpack 설정

ts-loaderbabel-loader를 함께 사용하면서 빌드 시간이 2배 늘었다. transpileOnly 옵션으로 타입 체크를 분리해 해결했다.

결과

3개월간 50% 정도 마이그레이션했다. 런타임 에러는 체감상 30% 줄었고, IDE 자동완성으로 생산성도 올랐다. 다만 학습 곡선과 초기 세팅 비용은 분명 존재했다. 팀원들의 TypeScript 이해도를 높이는 것이 다음 과제다.

프로젝트에 TypeScript 3.5 도입하며 겪은 것들