[Typescript] 이펙티브 타입스크립트 정리(아이템57 ~ 62)

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

타입스크립트 코드를 실행한다는 것은, 엄밀히 말해 타입스크립트 컴파일러가 생성한 자바스크립트 코드를 실행하는 것이다. 이러한 것은 디버깅이 필요한 시점에 깨닫게 된다. 디버깅 시점의 변환된 JS 코드는 복잡해서 디버깅이 어렵다. 그렇기 때문에 소스맵(source) 이라는 해결책이 나왔다. 소스맵은 변환된 코드의 위치와 심벌을 원본 코드에 매핑한다.

타입스크립트가 소스맵을 생성하게 하려면 tsconfig.json에서 sourceMap 옵션을 설정해야 한다.

{
  "compileOptions":{
    "sourceMap" : true
  }
}

이렇게 설정 후 컴파일하면 .ts파일에 대해서 .js.js.map 두 개의 파일이 생성된다.

요약

  • 원본 코드가 아닌 변환된 자바스크립트 코드를 디버깅하지 말자. 소스맵을 사용해서 런타임에 타입스크립트 코드를 디버깅하자.
  • 소스맵이 최종적으로 변환된 코드에 완전히 매핑되었는지 확인하자.
  • 소스맵에 원본 코드가 그대로 포함되도록 설정되어 있을 수 있다. 공개되지 않도록 설정을 확인하자.

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

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

타입스크립트는 타입 체크 기능 외에 타입스크립트 코드를 특정 버전 JS로 컴파일하는 기능이 있다. 그래서 타입스크립트 컴파일러를 트랜스파일러(transpiler)로 활용하는 것도 가능하다. TS로 마이그레이션 할 때, 막막하다면 옛날 버전의 JS를 최신 버전이 JS로 바꾸는 작업부터 시작하면 된다. 타입스크립트를 도입할 때 모던 자바스크립트(ES6)의 기능을 아는 것은 도움이 된다.

ECMAScript 모듈 사용하기

ES2015 이전에는 코드를 개별 모듈로 분할하는 방법이 없었지만 importexport가 표준이 되었다. 마이그레이션 대상이 비표준 모듈 시스템을 사용 중이라면 전환하는 것이 필수적이다.

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

과거에는 프로토타입 기반 객체 모델을 사용했지만, ES2015에서는 프로토타입 모델보다는 클래스 기반 모델을 사용한다.

하지만 최근에는 클래스 문법도 많이 사용되지 않고 함수 컴포넌트가 많이 사용되는 추세이다.

var 대신 let/const 사용하기

var 키워드의 스코프 규칙에 문제가 있기 때문에 letconst를 사용하면 스코프 문제를 피할 수 있다.

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

과거에는 for루프를 많이 사용했지만, 모던 JS에는 for-of 루프가 존재한다. for-of루프는 코드가 짧고 인덱스 변수를 사용하지 않아서 실수를 줄일 수 있다. 인덱스가 필요할 땐, forEach를 사용하는 것이 좋다. for-in의 경우는 몇가지 문제가 있어서 사용하지 않는 것이 좋다.

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

this키워드는 일반적인 변수들과는 다른 스코프 규칙을 가지기 때문에, JS에서는 가장 어려운 개념 중 하나이다. 이 때 화살표 함수를 사용하면 상위 스코프의 this를 유지할 수 있다. 인라인에서는 일반 함수보다 화살표 함수가 더 직관적이고 코드도 간결해지기 때문에 가급적 화살표 함수를 사용하는 것이 좋다. 컴파일러 옵션에 noImplicitThis를 설정하면 타입스크립트가 this 바인딩 관련된 오류를 표시해주므로 설정하는 것이 좋다.

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

변수와 객체 속성의 이름이 같으면 const pt = {x,y,z} 와 같이 표현 가능하다. 이런 것을 단축 객체 표현(compact object literal)이라고 한다. 이 반대는 객체 구조 분해(object destructuring)이다.

declare let obj: { props: { a: string; b: number } }
const props = obj.props
const a = props.a
const b = props.b

declare let obj: { props: { a: string; b: number } }
const { props } = obj
const { a, b } = props

구조 분해 문법은 위와 같이 사용하고 기본 값을 지정할 수도 있다.

declare let obj: { props: { a: string; b: number } }
const { a = 'default' } = obj.props

배열도 동일하게 구조 문해 문법을 사용할 수 있다. 매개변수도 또한 가능하다.

declare let obj: { props: { a: string; b: number } }
const point = [1, 2, 3]
const [x, y, z] = point
const [, a, b] = point // Ignore the first one

declare let obj: { props: { a: string; b: number } }
const points = [
  [1, 2, 3],
  [4, 5, 6],
]
points.forEach(([x, y, z]) => console.log(x + y + z))
// Logs 6, 15

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

함수의 모든 매개변수는 선택적(생략 가능)이며, 매개변수를 지정하지 않으면 undefined로 간주된다. 모던 JS에서는 매개변수에 기본값을 직접 지정할 수 있다. 매개변수에 기본 값을 지정하면 코드가 간결해지고 base가 선택적 매개변수라는 것을 명확히 나타낼 수 있다.

function parseNum(str, base = 10) {
  return parseInt(str, base)
}

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

async와 await를 사용하면 코드가 간결해져서 실수를 방지할 수 있고, 비동기 코드에 타입 정보가 전달되어 타입 추론이 가능해진다.

function getJSON(url: string) {
  return fetch(url).then(response => response.json())
}
function getJSONCallback(url: string, cb: (result: unknown) => void) {
  // ...
}

async function getJSON(url: string) {
  const response = await fetch(url)
  return response.json()
}

연관 배열에 객체 대신 Map과 Set 사용하기

인덱스 시그니처는 편리하지만 몇 가지 문제가 있다. 특정 문자열이 주어질 때, 원치 않는 값과 타입을 받을 수 있기 때문에, 이런 문제를 방지하기 위해 Map을 사용하는 것이 좋다.

function countWordMap(text: string) {
  const counts = new Map<string, number>()
  for (const word of text.split(/[\s,.]+/)) {
    counts.set(word, 1 + (counts.get(word) || 0))
  }
  return counts
}

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

ES5 에서는 버그가 될 수 있는 코드 패턴에 오류를 표시해주는 엄격 모드(strict mode)가 도입되었는데 코드의 제일 처음에 'use strict'와 같이 표시하면 활성화 된다. 하지만 TS에서는 훨씬 더 엄격한 체크를 하기 때문에 굳이 쓸 필요가 없다.

요약

  • TS 개발 환경은 모던 JS도 실행할 수 있으므로 모던 JS의 최신 기능들을 적극적으로 사용해야 한다. 코드 품질 향상과 TS의 타입 추론에도 도움이 된다.
  • TS 개발 환경에서 컴파일러와 언어 서비스를 통해 클래스, 구조분해, async/await 같은 기능을 쉽게 배울 수 있다.
  • ‘use strict’는 타입스크립트 컴파일러 수준에서 사용되어 코드에서 제거해야 한다.
  • TC39의 깃험 저장소와 TS의 릴리즈 노트를 통해 최신기능 확인이 가능하다.

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

@ts-check 지시자를 사용하면 타입스크립트 전환시에 어떤 문제가 발생하는지 미리 알 수 있다. 하지만 이 지시자는 매우 느슨한 수준으로 타입 체크를 수행하고, 심지어 noImplicitAny 설정을 해제한 것보다 헐거운 체크를 수행한다.

// @ts-check
const person = { first: 'Grace', last: 'Hopper' }
2 * person.first
// ~~~~~~~~~~~~ The right-hand side of an arithmetic operation must be of type
//              'any', 'number', 'bigint', or an enum type

@ts-check 지시자를 통해 타입 불일치나 함수의 매개변수 개수 불일치 같은 간단한 오류 외에도 아래와 같은 오류들을 찾아낼 수 있다.

선언되지 않은 전역 변수

변수를 선언할 때 보통은 let이나 const를 사용하지만 어딘가에 숨어있는 변수(HTML파일내의