이 장에선 DOM과 관련한 내용으로 예시를 바로 살펴보자.
function handleDrag(eDown: Event) {
const targetEl = eDown.currentTarget;
targetEl.classList.add('dragging');
const dragStart = [eDown.clientX, eDown.clientY];
const handleUp = (eUp: Event) => {
targetEl.classList.remove('dragging');
targetEl.removeEventListener('mouseup', handleUp);
const dragEnd = [eUp.clientX, eUp.clientY];
console.log('dx, dy = ', [0, 1].map(i => dragEnd[i] - dragStart[i]));
}
targetEl.addEventListener('mouseup', handleUp);
}
const div = document.getElementById('surface');
div.addEventListener('mousedown', handleDrag);
위에서 볼 수 있듯이, 위의 코드는 사진처럼 타입스크립트에서 많은 에러를 발생시킨다.
targetEl
은 현재 EventTarget | null
로 추론되며 EventTarget
타입에 classList
속성이 없어서 에러를 발생시킨다. 따라서 타겟의 타입을 알고 있다면 더 구체적인 타입 추론을 위해 정확한 타입으로 타입 단언문을 사용하거나 null
값이 포함될 여지가 있다면 if
문으로 분기처리를 해주어야한다.clientX
와 clientY
와 관련한 오류로 eDown
안에 clientX
와 같은 속성이 정의되어 있지 않기 때문이다. 현재 eDown
의 타입은 Event
로, 가장 추상화된 이벤트이며 더 구체적인 타입을 위해선 드래그와 관련하여 MouseEvent
를 타입으로 지정해주어야 한다. 마찬가지로 handleUp
함수 안의 eUp
도 MouseEvent
타입을 구체적으로 지정해줌으로써 오류를 해결할 수 있다.DOM 타입의 계층 구조를 알고, 엘리먼트와 이벤트에 충분히 구체적인 타입정보를 사용하거나 타입스크립트가 추론할 수 있도록 문맥 정보를 활용하자!
타입스크립트에는 public, protected, private
와 같은 접근 제어자를 사용할 수 있다.
그러나 결국에 컴파일 되는 과정에서 제거되기 때문에 자바스크립트 코드에서는 접근할 수 있다. 또한 타입스크립트의 단언문을 통해서도 우회할 수 있다.
따라서 정보를 숨기기 위해 사용하는 방법은 2가지가 있다.
클로저의 경우 생성자 외부에서 접근할 수 없으며, 인스턴스를 생성할 때마다 각 메서드의 복사본이 생성되기 때문에(메서드 정의가 생성자 내부에 존재하는 경우) 메모리를 낭비하게 된다. 또한 서로의 비공개 데이터에 접근하는 것이 불가능하기 때문에 철저하게 비공개이면서 동시에 불편함이 따른다.
프라이빗 필드는 클래스 메서드나 동일한 클래스의 개별 인스턴스끼리는 접근이 가능하다.
결국 이 아이템에선 접근 제어자로 데이터를 감추려고 해서는 안된다는 말을함
코드를 실행하면 이 순간은 런타임 환경이 되며, 디버깅이 필요한 시점(런타임)에 타입스크립트가 실행되는 것이 아닌 자바스크립트가 동작한다는 것을 알게 될 것이다.
이러한 상황에서 코드의 어디가 잘못되었는지 확인하려면 소스맵이 필요하다.
tsconfig
에서 컴파일러옵션에 소스맵true
속성을 주면 됨
소스맵을 사용해서 제대로 된 타입스크립트 디버깅 환경을 구축하자. 또한 소스맵 사용 시 원본 코드가 그대로 포함되도록 설정되지 않게 공개되지 않도록 설정을 확인하자.
깃헙에 있는 JS 프로젝트에서 발견된 버그의 15%와 에어비앤비에서 진행된 프로젝트들을 분석해보니 버그의 38%는 타입스크립트를 사용했다면 미리 방지할만한 것들이었다.
이러한 결과를 토대로 기존 프로젝트를 타입스크립트로 점진적 마이그레이션을 하는 방법을 8장에서 다룬다.
타입스크립트는 타입 체크 기능 이외에 타입스크립트 코드를 특정 자바스크립트로 컴파일 하는 기능도 가지고 있다. 즉, 자바스크립트 트랜스파일러로 사용할 수 있다. 이 의미는 최신 버전의 자바스크립트 코드를 옛날 버전의 자바스크립트 코드로 변환할 수 있다는 의미이다.
옛날 버전의 자바스크립트 코드를 최신 버전의 자바스크립트를 바꾸는 작업또한 TS 마이그레이션 과정이라고 할 수 있다.
이 장에는 위에서 언급한 내용에 따라 최신 자바스크립트 문법에 대해서 이야기한다. 대부분의 내용은 모던 자바스크립트 Deep Dive에서 공부한 내용들이라 필요한 부분만 정리하겠다.
최신 문법
this
문제와 연관지어서 설명하고 컴파일러 옵션에 noImplictThis
또는 strict
를 설정하면 TS
가 this
바인딩 관련 오류를 표시해줌)async/await
사용하기constructor
라는 Object.prototype
을 예시로 듦)use strict
넣지 않기(TS
의 안전성 검사가 엄격 모드보다 훨씬 더 엄격한 체크를 하기 때문)모던 자바스크립트의 최신 기능들을 적극적으로 사용하면 코드 품질을 향상시킬 수 있고, 타입스크립트의 타입 추론도 더 나아진다.
타입스크립트로의 전환에 앞서 @ts-check
로 먼저 시험해 볼 수 있다. 그러나 이 방법은 noImplicitAny
설정을 해제한 것보다 헐거운 체크를 수행한다는 것을 알아야한다.
@ts-check
가 해주는 검사
types.d.ts
파일을 이용하여 변수 인식시키거나 트리플 슬래시로 명시적인 임포트)JSDoc
을 통한 타입 단언 대체)마이그레이션의 궁극적인 목표는 모든 코드를 TS 기반으로 전환되는 것이므로 JSDoc
과 @ts-check
같은 중간단계를 너무 공들일 필요는 없다.
타입스크립트와 자바스크립트가 공존하는 방법의 핵심 → allowJs
컴파일러 옵션
TS 파일과 JS 파일을 서로 임포트할 수 있게 해주고, 기존 빌드 과정에 TS 컴파일러를 추가하기 위해 이 옵션이 필요하다. 모듈단위에서 테스트하는 과정에서도 필요
allowJs
적용하는 법
npm install —save-dev tsify
를 통해)outDir
옵션(이 옵션은 TS
가 outDir
에 지정된 디렉터리에 소스 디렉터리와 비슷한 구조로 코드 생성한후 이 디렉터리를 대상으로 빌드 체인 실행)TS로 대규모 마이그레이션 하는 동시에 빌드와 테스트가 동작하게 하는 것이 힘들지만, 제대로 하기 위해서는 반드시 필요하다. (테스트와 빌드 체인에 TS 적용 필요!)
점진적 마이그레이션을 할 때는 모듈 단위로 각개격파하는 것이 이상적이다.
따라서 다른 모듈에 의존하지 않는 최하단 모듈부터 작업을 시작해서 의존성의 최상단에 있는 모듈을 마지막으로 완성하는 것이 일반적이다. →
madge
라는 도구를 이용한 의존성 관계 시각화를 하면 많이 도움이 됨!
서드 파티와 같은 라이브러리 → @types 모듈을 통해 해결
외부 API → API에 대한 사양을 기반으로 타입 정보 생성
마이그레이션시에 타입정보만 추가하고 리팩터링을 해서는 안된다. → Twospoon 프로젝트에서 많이 깨달음...
목표는 코드 개선이 아닌 TS로 전환하는 것임을 명시!
선언되지 않은 클래스 멤버
js와 달리 ts 클래스에선 클래스 멤버 변수를 선언해주어야하는데 빠른 수정 기능을 이용해서 추가하며, 정확하게 추론되지 않은 타입들을 수정해주어야한다.
타입이 바뀌는 값
당장의 마이그레이션이 중요하면 타입 단언문을 사용하고 이후 문제를 제대로 해결해야 한다. 또는 JSDoc과 같은 타입 정보를 추가한 상태이면 이를 이용한 빠른 수정이 가능하다.
최종적으로 테스트 코드는 의존성 관계도의 최상단에 위치하기 때문에 마이그레이션의 마지막 단계로 수정하면 된다.
마이그레이션의 마지막 단계로 noImplicitAny
를 설정하여 타입 선언과 관련된 실제 오류를 해결해야 한다.
처음에는 로컬에서부터 타입 오류를 점진적으로 수정하고, 그 강도를 점점 높여서(
noImplicitAny
→strict: true
) 팀원들이 TS에 익숙해질 시간을 주는 것이 좋다.