8장. 타입스크립트로 마이그레이션 하기
60. allowJs로 TS와 JS 같이 사용 (사전 작업)
SUMMARY
- allowJs 옵션 → .ts & .js 확장자 동시 사용 가능
- test & build 체인에 TS 우선 적용 필요
•
대규모 프로젝트의 경우 점진적 마이그레이션이 필요 → JS, TS 파일이 공존해야 함
◦
allowJS 옵션: JS파일과 TS파일을 서로 임포트 할 수 있게 함
◦
test & build 체인에 TS 우선 적용 필요
61. 의존성 관계에 따라 모듈 단위로 전환 (순서, 수치화)
SUMMARY
- 의존성이 낮은 모듈부터 작업 시작 (서드파티, API 호출, 의존성 최하단 모듈)
- 리팩터링은 추후에 / TS 전환에 따른 오류 조심
•
점진적 마이그레이션 시에는 모듈 단위 변경이 이상적
◦
일부 모듈 변경 시 의존(해당 모듈 내에서 임포트 해오는) 관련 오류 발생
◦
다른 모듈에 의존하지 않는 (의존성이 낮은) 부분부터 작업 시작 필요
▪
서드파티 라이브러리 (프로젝트 내 모듈에 의존 X) → @types 설치
▪
외부 API (프로젝트 내 모듈에 의존 X) → API 사양 기반 타입 정보 추가
▪
의존성 최하단 모듈 → 의존성 관계를 시각화
의존성 시각화 툴 활용 (madge 등)
•
의존성 최하단에는 주로 유틸리티 모듈이 존재
•
오류 & 주의사항 (4가지)
오류 ❶ - 선언되지 않은 클래스 멤버
•
class Greeting {
constructor(name) {
this.greeting = 'Hello'
// ~~~~~~~~ Property 'greeting' does not exist on type 'Greeting'
this.name = name
// ~~~~ Property 'name' does not exist on type 'Greeting'
}
// ...
}
TypeScript
복사
•
// 클래스 멤버 변수를 명시적으로 선언
class Greeting {
greeting: string;
name: any;
constructor(name) {
this.greeting = 'Hello'
this.name = name
}
// ...
}
TypeScript
복사
오류 ❷ - 타입이 바뀌는 값
•
const state = {}
state.name = 'New York'
// ~~~~ Property 'name' does not exist on type '{}'
state.capital = 'Albany'
// ~~~~~~~ Property 'capital' does not exist on type '{}'
TypeScript
복사
•
interface State {
name: string
capital: string
}
const state = {} as State
state.name = 'New York' // OK
state.capital = 'Albany' // OK
TypeScript
복사
주의할 점 ❶ - JS에서 추가 된 타입 정보는 무효화 됨
◦
JS 내에 JSDoc, @ts-check 로 추가된 타입 정보는 무효화 됨 → 빠른 수정을 통한 TS 전환
주의할 점 ❷ - 마이그레이션 중에는 다른 리팩터링 X
◦
◦
•
마지막 단계 - 테스트 코드 TS 전환
◦
테스트 코드는 항상 의존성 코드에 최상단에 존재
◦
변환 가능한 최하단의 모듈부터 테스트를 수행하며 변환
62. 마이그레이션의 완성을 위해 noImplicityAny 설정 (완성도)
SUMMARY
- 마이그레이션 후 noImplicityAny 적용 → 숨은 타입 오류 발견 가능
- noImplicityAny 로컬에 우선 적용 → 점진적 마이그레이션 가능
noImplicityAny의 활용 ❶ - 타입 오류 발견
// tsConfig: {"noImplicitAny":false,"strictNullChecks":false}
class Chart {
indices: any // index의 복수형
// ...
}
class Chart {
// 빠른 수정에서 any로 추론 된 indices를 임의로 수정
// 사실 indeices는 이차원 배열 (number[][])
indices: number[]
getRanges() {
// r은 number[] 형태
for (const r of this.indices) {
const low = r[0] // Type is any
const high = r[1] // Type is any
// ...
}
}
}
// 만약 noImplicityAny가 설정 시 정확한 타입체크가 되어 '제대로 된 오류 메세지'보여줌
// ~~~~ Element implicitly has an 'any' type because
// type 'Number' has no index signature
TypeScript
복사
noImplicityAny의 활용 ❷ - 점진적 마이그레이션
•
처음에는 noImplicityAny 를 로컬에 먼저 설정 후 작업하는 것이 좋음
◦
원격 설정에는 변화가 없어 빌드가 실패 X
◦
로컬에만 발생한 오류를 수정해 커밋 가능 (점진적 마이그레이션)
◦
로컬 오류의 개수를 진척도로 활용 가능
•
타입 체커의 강도는 사람들의 숙련도에 따라 차차 올려야 함
◦
noImplicityAny 는 상당히 엄격한 설정 (대부분의 타입 체크를 적용한 수준) → “strict”: true 가 가장 강력
/* 엄격한 유형 검사 옵션
* -------------------------------------------------------------------------------------------- */
"strict": true, /* 모든 엄격한 유형 검사 옵션 활성화 */
// "noImplicitAny": true, /* 명시적이지 않은 'any' 유형으로 표현식 및 선언 사용 시 오류 발생 */
// "strictNullChecks": true, /* 엄격한 'null' 검사 사용 */
// "strictFunctionTypes": true, /* 엄격한 함수 유형 검사 사용 */
// "strictBindCallApply": true, /* 엄격한 'bind', 'call', 'apply' 함수 메서드 사용 */
// "strictPropertyInitialization": true, /* 클래스에서 속성 초기화 엄격 검사 사용 */
// "noImplicitThis": true, /* 명시적이지 않은 'any'유형으로 'this' 표현식 사용 시 오류 발생 */
// "alwaysStrict": true, /* 엄격모드에서 구문 분석 후, 각 소스 파일에 "use strict" 코드를 출력 */
/* 추가 검사 옵션
* -------------------------------------------------------------------------------------------- */
// "noUnusedLocals": true, /* 사용되지 않은 로컬이 있을 경우, 오류로 보고 */
// "noUnusedParameters": true, /* 사용되지 않은 매개변수가 있을 경우, 오류로 보고 */
// "noImplicitReturns": true, /* 함수가 값을 반환하지 않을 경우, 오류로 보고 */
// "noFallthroughCasesInSwitch": true, /* switch 문 오류 유형에 대한 오류 보고 */
// "noUncheckedIndexedAccess": true, /* 인덱스 시그니처 결과에 'undefined' 포함 */
TypeScript
복사
Reference