[이펙티브 타입스크립트] - 9주차

[아이템55] DOM 계층 구조 이해하기

이 장에선 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);

위에서 볼 수 있듯이, 위의 코드는 사진처럼 타입스크립트에서 많은 에러를 발생시킨다.

  1. targetEl은 현재 EventTarget | null로 추론되며 EventTarget 타입에 classList 속성이 없어서 에러를 발생시킨다. 따라서 타겟의 타입을 알고 있다면 더 구체적인 타입 추론을 위해 정확한 타입으로 타입 단언문을 사용하거나 null 값이 포함될 여지가 있다면 if문으로 분기처리를 해주어야한다.
  2. 두 번째 clientXclientY와 관련한 오류로 eDown안에 clientX 와 같은 속성이 정의되어 있지 않기 때문이다. 현재 eDown의 타입은 Event로, 가장 추상화된 이벤트이며 더 구체적인 타입을 위해선 드래그와 관련하여 MouseEvent를 타입으로 지정해주어야 한다. 마찬가지로 handleUp함수 안의 eUpMouseEvent 타입을 구체적으로 지정해줌으로써 오류를 해결할 수 있다.

DOM 타입의 계층 구조를 알고, 엘리먼트와 이벤트에 충분히 구체적인 타입정보를 사용하거나 타입스크립트가 추론할 수 있도록 문맥 정보를 활용하자!

[아이템56] 정보를 감추는 목적으로 private 사용하지 않기

타입스크립트에는 public, protected, private 와 같은 접근 제어자를 사용할 수 있다.

그러나 결국에 컴파일 되는 과정에서 제거되기 때문에 자바스크립트 코드에서는 접근할 수 있다. 또한 타입스크립트의 단언문을 통해서도 우회할 수 있다.

따라서 정보를 숨기기 위해 사용하는 방법은 2가지가 있다.

  • 클로저
  • 접두사로 #을 붙여 프라이빗 필드 사용

클로저의 경우 생성자 외부에서 접근할 수 없으며, 인스턴스를 생성할 때마다 각 메서드의 복사본이 생성되기 때문에(메서드 정의가 생성자 내부에 존재하는 경우) 메모리를 낭비하게 된다. 또한 서로의 비공개 데이터에 접근하는 것이 불가능하기 때문에 철저하게 비공개이면서 동시에 불편함이 따른다.

프라이빗 필드는 클래스 메서드나 동일한 클래스의 개별 인스턴스끼리는 접근이 가능하다.

결국 이 아이템에선 접근 제어자로 데이터를 감추려고 해서는 안된다는 말을함

[아이템57] 소스맵을 사용하여 타입스크립트 디버깅하기

코드를 실행하면 이 순간은 런타임 환경이 되며, 디버깅이 필요한 시점(런타임)에 타입스크립트가 실행되는 것이 아닌 자바스크립트가 동작한다는 것을 알게 될 것이다.

이러한 상황에서 코드의 어디가 잘못되었는지 확인하려면 소스맵이 필요하다.

tsconfig에서 컴파일러옵션에 소스맵 true 속성을 주면 됨

소스맵을 사용해서 제대로 된 타입스크립트 디버깅 환경을 구축하자. 또한 소스맵 사용 시 원본 코드가 그대로 포함되도록 설정되지 않게 공개되지 않도록 설정을 확인하자.

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

깃헙에 있는 JS 프로젝트에서 발견된 버그의 15%와 에어비앤비에서 진행된 프로젝트들을 분석해보니 버그의 38%는 타입스크립트를 사용했다면 미리 방지할만한 것들이었다.

이러한 결과를 토대로 기존 프로젝트를 타입스크립트로 점진적 마이그레이션을 하는 방법을 8장에서 다룬다.

[아이템58] 모던 자바스크립트로 작성하기

타입스크립트는 타입 체크 기능 이외에 타입스크립트 코드를 특정 자바스크립트로 컴파일 하는 기능도 가지고 있다. 즉, 자바스크립트 트랜스파일러로 사용할 수 있다. 이 의미는 최신 버전의 자바스크립트 코드를 옛날 버전의 자바스크립트 코드로 변환할 수 있다는 의미이다.

옛날 버전의 자바스크립트 코드를 최신 버전의 자바스크립트를 바꾸는 작업또한 TS 마이그레이션 과정이라고 할 수 있다.

이 장에는 위에서 언급한 내용에 따라 최신 자바스크립트 문법에 대해서 이야기한다. 대부분의 내용은 모던 자바스크립트 Deep Dive에서 공부한 내용들이라 필요한 부분만 정리하겠다.

최신 문법

  • ECMAScript 모듈 사용
  • 프로토타입 대신 클래스 사용
  • var대신 let/const 사용(호이스팅 관점에서)
  • for(;;)대신 for-of 또는 forEach 사용
  • 함수 표현식보다 화살표 함수 사용(this문제와 연관지어서 설명하고 컴파일러 옵션에 noImplictThis 또는 strict를 설정하면 TSthis 바인딩 관련 오류를 표시해줌)
  • 단축 객체 표현과 구조 분해 할당 사용
  • 함수 매개변수 기본값 사용
  • 저수준 프로미스나 콜백 대신 async/await 사용하기
  • 연관 배열에 객체 대신 Map과 Set사용(constructor라는 Object.prototype을 예시로 듦)
  • 타입스크립트에 use strict 넣지 않기(TS의 안전성 검사가 엄격 모드보다 훨씬 더 엄격한 체크를 하기 때문)

모던 자바스크립트의 최신 기능들을 적극적으로 사용하면 코드 품질을 향상시킬 수 있고, 타입스크립트의 타입 추론도 더 나아진다.

[아이템59] 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기

타입스크립트로의 전환에 앞서 @ts-check로 먼저 시험해 볼 수 있다. 그러나 이 방법은 noImplicitAny 설정을 해제한 것보다 헐거운 체크를 수행한다는 것을 알아야한다.

@ts-check가 해주는 검사

  • 타입 불일치
  • 함수의 매개변수 개수 불일치
  • 선언되지 않은 전역 변수(types.d.ts 파일을 이용하여 변수 인식시키거나 트리플 슬래시로 명시적인 임포트)
  • 알 수 없는 라이브러리(제이쿼리를 예시로 제이쿼리 타입 선언을 설치하는 방법으로 타입 체킹 가능)
  • DOM 문제(타입스크립트가 아니어서 타입단언은 못하지만, JSDoc을 통한 타입 단언 대체)
  • 부정확한 JSDoc(변수의 타입과 정확한 타입으로 수정함으로 써 해결할 수 있고, JSDoc 자동 생성 기능은 타입 정보를 빠르게 추가할 수 있기 때문에 유용하지만, 잘 동작하지 않은 경우가 있어 유의해서 사용!)

마이그레이션의 궁극적인 목표는 모든 코드를 TS 기반으로 전환되는 것이므로 JSDoc@ts-check 같은 중간단계를 너무 공들일 필요는 없다.

[아이템60] allowJs로 타입스크립트와 자바스크립트 같이 사용하기

타입스크립트와 자바스크립트가 공존하는 방법의 핵심 → allowJs 컴파일러 옵션

TS 파일과 JS 파일을 서로 임포트할 수 있게 해주고, 기존 빌드 과정에 TS 컴파일러를 추가하기 위해 이 옵션이 필요하다. 모듈단위에서 테스트하는 과정에서도 필요

allowJs 적용하는 법

  • 번들러, 플러그인 방식으로 통합되어 있다면 간단히 적용가능(npm install —save-dev tsify 를 통해)
  • 프레임워크 없이 빌드 체인 직접 구성한 경우 outDir 옵션(이 옵션은 TSoutDir에 지정된 디렉터리에 소스 디렉터리와 비슷한 구조로 코드 생성한후 이 디렉터리를 대상으로 빌드 체인 실행)

TS로 대규모 마이그레이션 하는 동시에 빌드와 테스트가 동작하게 하는 것이 힘들지만, 제대로 하기 위해서는 반드시 필요하다. (테스트와 빌드 체인에 TS 적용 필요!)

[아이템61] 의존성 관계에 따라 모듈 단위로 전환하기

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

따라서 다른 모듈에 의존하지 않는 최하단 모듈부터 작업을 시작해서 의존성의 최상단에 있는 모듈을 마지막으로 완성하는 것이 일반적이다. → madge라는 도구를 이용한 의존성 관계 시각화를 하면 많이 도움이 됨!

서드 파티와 같은 라이브러리 → @types 모듈을 통해 해결

외부 API → API에 대한 사양을 기반으로 타입 정보 생성

마이그레이션시에 타입정보만 추가하고 리팩터링을 해서는 안된다. → Twospoon 프로젝트에서 많이 깨달음...

목표는 코드 개선이 아닌 TS로 전환하는 것임을 명시!

선언되지 않은 클래스 멤버

js와 달리 ts 클래스에선 클래스 멤버 변수를 선언해주어야하는데 빠른 수정 기능을 이용해서 추가하며, 정확하게 추론되지 않은 타입들을 수정해주어야한다.

타입이 바뀌는 값

당장의 마이그레이션이 중요하면 타입 단언문을 사용하고 이후 문제를 제대로 해결해야 한다. 또는 JSDoc과 같은 타입 정보를 추가한 상태이면 이를 이용한 빠른 수정이 가능하다.

최종적으로 테스트 코드는 의존성 관계도의 최상단에 위치하기 때문에 마이그레이션의 마지막 단계로 수정하면 된다.

[아이템62] 마이그레이션의 완성을 위해 noImplicitAny 설정하기

마이그레이션의 마지막 단계로 noImplicitAny를 설정하여 타입 선언과 관련된 실제 오류를 해결해야 한다.

처음에는 로컬에서부터 타입 오류를 점진적으로 수정하고, 그 강도를 점점 높여서(noImplicitAnystrict: true) 팀원들이 TS에 익숙해질 시간을 주는 것이 좋다.

좋은 웹페이지 즐겨찾기