타입스크립트로 마이그레이션하기

2024. 1.24.

typescript

migration

INDEX

1. 마이그레이션을 하기에 앞서


큰 프로젝트를 타이스크립트로 마이그레이션 하는 작업은 어렵지만, 프로젝트 품질을 크게 개선할 수 있는 가능성을 열어 줍니다.


점진적 마이그레이션

프로젝트를 타입스크립트로 마이그레이션 하기로 결정했다면, 점진적 마이그레이션을 통해 실험해 보고 테스트해야 합니다.


진행 상황 모니터링

마이그레이션 작업은 오랜 시간이 필요하기 때문에, 진행 상황을 모니터링하고 추적해서 중복된 작업을 방지해야 합니다.

마이그레이션의 진행 상황을 수치화하여 눈으로 볼 수 있게 하면 진행상황을 파악하기도 쉽고, 팀원들을 동기부여하는 효과도 얻을 수 있습니다.



2. 모던 자바스크립트로 작성하기


마이그레이션을 어디서부터 시작해야 할 지 몰라 막막하다면 옛날 버전의 자바스크립트 코드를 최신 버전의 자바스크립트로 바꾸는 작업부터 시작해 보는 것도 좋습니다.

타입스크립트는 자바스크립트의 상위집합이기 떄문에 코드를 최선 버전으로 바꾸다 보면 타입스크립트의 일부를 저절로 익힐 수 있게 됩니다.

옛날 버전의 자바스크립트 코드를 최신 버전의 자바스크립트로 바꾸는 작업은 타입스크립트로 전환하는 작업의 일부로 볼 수 있습니다.


ECMAScript 모듈 사용하기

ES2015부터 import와 export를 사용하는 ESM이 표준이 되었습니다.

만약 마이그레이션 대상인 자바스크립트 코드가 단일 파일이거나 비표준 모듈 시스템을 사용 중이라면 ESM으로 전환하는 것이 좋습니다.

ESM은 타입스크립트에서도 잘 동작하며, 모듈 단위로 전환할 수 있게 해 주기 때문에 점진적 마이그레이션이 월활해집니다.

ESM을 사용하기 위해서는 프로젝트 종류에 따라 webpack이나 ts-node 같은 도구가 필요한 경우도 있습니다.


프로토타입 대신 클래스 사용하기

마이그레이션하려는 코드에서 단순한 객체를 다룰 떄 프로토타입을 사용하고 있었다면 클래스로 바꾸는 것이 좋습니다.

프토토타입으로 구현한 Person 객체보다 클래스로 구현한 Person 객체가 문법이 간결하고 직관적입니다.


var 대신 let/const 사용하기

var 키워드를 let이나 const로 변경하면, 일부 코드에서 타입스크립트가 오류를 표시할 수도 있습니다. 오류가 발생한 부분은 잠재적으로 스코프 문제가 존재하는 코드이기 때문에 수정해야 합니다.


중첩함수 대신 함수 표현식 사용하기

중첩함수의 호이스팅은 실행 순서를 예상하기 어렵게 만들고 직관적이지 않습니다.

대신 함수 표현식(const bar = () => { … })을 사용하여 호이스팅 문제를 피하는 것이 좋습니다.


for(;;) 대신 for-of 또는 배열 메서드 사용하기

for-of 루프는 코드가 짧고 인덱스 변수를 사용하지도 않기 때문에 실수를 줄일 수 있습니다. 인덱스 변수가 필요한 경우엔 forEach 메서드를 사용하면 됩니다.


함수 표현식보다 화살표 함수 사용하기

일반적으로는 this가 클래스 인스턴스를 참조하는 것을 기대하지만 예상치 못한 결과가 나오는 경우도 있습니다.

인라인(또는 콜백)에서는 일반 함수보다 화살표 함수가 더 직관적이며 코드도 간결해지기 때문에 가급적 화살표 함수를 사용하는 것이 좋습니다.

컴파일러 옵션(tsconfig.json)에 noImplicitThis(또는 strict)를 설정하면, 타입스크립트가 this 바인딩 관련된 오류를 표시해 주므로 설정하는 것이 좋습니다.


단축 객체 표현과 구조 분해 할당 사용하기

단축 객체 표현과 구조 분해 할당을 사용하면 코드가 더 간결하고 중복된 이름을 생략하기 때문에 가독성이 좋고 실수가 적습니다.


함수 매개변수 기본값 사용하기

매개변수에 기본값을 지정하면 코드가 간결해질 뿐만 아니라 해당 매개변수가 선택적 매개변수라는 것을 명확히 나타내는 효과도 줄 수 있습니다.

기본값을 지정하면 기본값을 기반으로 타입 추론이 가능하기 때문에, 타입스크립트로 마이그레이션할 때 매개변수에 타입 구문을 쓰지 않아도 됩니다.


저수준 프로미스나 콜백 대신 async/await 사용하기

async/await를 사용하면 코드가 간결해져서 버그나 실수를 방지할 수 있고, 비동기 코드에 타입 정보가 전달되어 타입 추론을 가능하게 합니다.


타입스크립트에 use strict 넣지 않기

타입스크립트에서 수행되는 안전성 검사(sanity check)가 엄격 모드보다 훨씬 더 엄격한 체크를 하기 때문에, 타입스크립트 코드에서 ‘use strict’는 무의미합니다.

코드에 ‘use strict’를 대신 컴파일러 옵션의 alwaysStrict 설정을 사용해야 합니다.



3. 타입스크립트 도입 전에 @ts-check로 시험해보기


@ts-check 지시자를 사용하면 타입스크립트 전환시에 어떤 문제가 발생하는지 미리 시험해 볼 수 있습니다.

@ts-check 지시자를 사용하여 타입 체커가 파일을 분석하고, 발견된 오류를 보고하도록 지시합니다.

그러나, @ts-check 지시자는 noImplicitAny 설정을 해제한 것 보다 헐거운 체크를 수행한다는 점을 주의해야 합니다.


선언되지 않은 전역 변수

어딘가에 숨어있는 변수가 있다면, 변수를 제대로 인식할 수 있게 별도로 타입 선언 파일을 만들어야 합니다.

d.ts 파일(정의 파일)을 통해 프로젝트 타입 선언의 초석을 만들 수 있습니다.


알 수 없는 라이브러리

서드파티 라이브러리의 경우 타입 정보를 알아야 합니다.

npmjs.com에서 ‘TS’로 표시된다면 타입스크립를 지원하는 라이브러리이며, ‘DT’로 표시되어 있다면 @types/라이브러리명을 통해 타입 선언을 추가로 설치해줘야 합니다.



4. allowJs로 타입스크립트와 자바스크립트 같이 사용하기


한꺼번에 타입스크트로 마이그레이션이 어려울 경우, 컴파일러 옵션의 allowJs를 통해 마이그레이션 기간 중에 자바스크립트와 타입스크립트가 동시에 동작할 수 있도록 해야 합니다.

대규모 마이그레이션 작업을 시작하기 전에, 테스트와 빌드 체인에 타입스크립트를 적용해야 합니다. (컴파일러 옵션의 outDir, target, module 활용)



5. 의존성 관계에 따라 모듈 단위로 전환하기


점진적 마이그레이션을 할 때는 모듈 단위로 각개격파하는 것이 이상적입니다.

의존성과 관련된 오류없이 작업하려면, 다른 모듈에 의존하지 않는 최하단 모듈부터 작업을 시작해서 의존성의 최상단에 있는 모듈을 마지막으로 완성해야 합니다.

외부 API의 타입 정보는 특별한 문맥이 없기 때문에 타입스크립트가 추론하기 어렵고, 그렇기 때문에 API에 대한 사양을 기반으로 타입 정보를 생성해야 합니다.

모듈 단위로 마이그레이션을 시작하기 전에, 모듈간의 의존성 관계를 시각화해 보면 많은 도움이 됩니다.

마이그레이션할 때는 타입 정보 추가만 해야 하고, 리팩터링은 해서는 안됩니다.

당장의 목표는 코드 개선이 아니라 타입스크립트로 전환하는 것임을 명심해야 합니다.

개선이 필요한 부분을 찾게 된다면 나중에 리팩터링할 수 있도록 목록을 만들어 둡시다.


선언되지 않은 클래스 멤버

자바스크립트는 클래스 멤버 변수를 선언할 필요가 없지만, 타입스크립트에서는 명시적으로 선언해야 합니다.
-> quick fix 기능을 통해 간단히 해결가능 합니다.
-> quick fix를 적용한 후에 속성을 훑어보고 any로 추론된 부분은 직접 수정해야 합니다.

자바스크립트 코드를 타입스크립트로 전환하다 보면, 잘못된 설계를 발견하는 효과가 있습니다.

하지만! 리팩터링을 하면 안 됩니다.
개선할 부분을 기록해 두고, 리팩터링은 타입스크립트 전환 작업이 완료된 후에 생각해야 합니다.


타입이 바뀌는 값

임시 방편으로 타입 단언문을 사용할 수도 있습니다. 당장은 마이그레이션이 중요하기 때문에 타입 단언문을 사용한 것이며, 마이그레이션이 완료된 후에는 문제를 제대로 해결해야 합니다.

테스트 코드

로직 코드가 테스트 코드에 의존하지 않기 때문에, 테스트 코드는 항상 의존성 관계도의 최상단에 위치하며 마이그레이션의 마지막 단계가 되는 것은 자연스러운 일입니다.

최하단의 모듈부터 타입스크립트로 전환하는 와중에도 테스트 코드는 변경되지 않고, 마이그레이션 기간 중에 테스트를 수행할 수 있습니다.


6. 마이그레이션의 완성을 위해 noImplicitAny 설정하기


noImplicitAny가 설정되지 않은 상태에서는 타입 선언에서 비롯되는 실제 오류가 숨어 있기 때문에 마이그레이션이 완료되었다고 할 수 없습니다.

빌드가 실패하는 것을 방지하기 위해, 처음에는 noImplicitAny를 로컬에만 설정하고 작업하는 것이 좋습니다.

타입 체커가 발생하는 오류의 개수는 noImplicitAny와 관련된 작업의 진척도를 나타내는 지표로 활용할 수 있습니다.

최종적으로 가장 강력한 설정은 “strict”: true 입니다.

타입체크의 강도를 조절하여 팀원들이 타입스크립트에 익숙해질 수 있도록 시간을 줄 수 있습니다.


참고자료

댄 밴더캄, ⌜이펙티브 타입스크립트⌟, 프로그래밍인사이트, 장원호 옮김, 2021