아이템 56 정보를 감추는 목적으로 private 사용하지 않기
자바스크립트는 클래스에 비공개 속성을 만들 수 없다. 비공개 속성임을 나태내기 위해 언더스코어(_)
를 접두사로 붙이던 것이 관례로 인정되어 왔던 것 뿐이다. 하지만 언더스코어를 붙이는 것은 비공개라고 표시한 것 뿐, 일반적인 속성과 동일하게 클래스 외부로 공개되어 있다.
1 | class Diary { |
타입스크립트에서 위의 예시처럼 public, protected, private 접근 제어자를 사용하기 때문에 규칙을 강제하는 것으로 오해하기 쉬운데 이는 타입스크립트 키워드이기 때문에 컴파일 후에 제거된다. 그래서 타입스크립트의 컴파일되면 위의 예제는 아래의 예제처럼 자바스크립트 코드(target=ES2017)로 변환된다.
1 | class Diary { |
컴파일 후에 확인해보면 타입스크립트 키워드인 private이 제거되었고, secret은 일반적인 속성이어서 접근할 수 있다. 즉 언더스코어의 관례처럼 타입스크립트의 접근 제어자들도 런타임에는 아무런 효력이 없다. 심지어 타입스크립트 상태에서도 단언문을 사용하면 private에 접근 가능하다.
1 | declare function hash(text: string): number |
따라서 정보를 감추기 위한 목적으로 private을 사용하면 안된다. 자바스크립트에서 정보를 숨기기 위한 가장 효과적은 방법은 클로저이다. 위의 예시는 생성자에서 클로저를 만드는 예시이다. 이렇게 작성하면 PasswordChecker의 생성자 외부에서 passwordHash 변수에 접근할 수 없어서 정보를 숨기는 목적은 달성하게 되는데, 주의사항이 있다. passwordHash를 생성자 외부에서 접근할 수 없기 때문에 passwordHash에 접근해야 하는 메서드는 생성자 내부에 정의되어야 한다. 또 메서드 정의가 생성자 내부에 존재하게 되면, 인스턴스를 생성할 때마다 각 메서드의 복사본이 생성되기 때문에 메모리를 낭비하게 된다. 클로저를 쓰지 않으면 현재 표준화가 진행 중인 비공개 필드 기능을 사용할 수 있는데, 접두사 #을 붙여서 타입 체크과 런타임 모두에서 비공개로 만드는 역할을 한다.
아이템 57 소스맵을 사용하여 타입스크립트 디버깅하기
타입스크립트 코드를 실행한다는 것은 타입스크립트 컴파일러가 생성한 자바스크립트 코드를 생성한다는 것이다. 그런데 변환된 자바스크립트 코드는 복잡해서 디버깅하기가 어렵다. 디버깅하기 쉽도록 해결책을 내놓은 것이 소스맵이다. 소스맵은 변환된 코드의 위치와 심벌들을 원본 코드의 원래 위치와 심벌들로 매핑한다. 보통 자바스크립트로 변환된 코드는 원본 코드와 거의 비슷해서 디버깅하기 쉽지만 복잡하게 변환된다면 소스맵이 필요하다. 타입스크립트가 소스맵을 생성할 수 있도록 tsconfig.json에 소스맵 옵션을 다음과 같이 설정한다.
1 | { |
아이템 58 모던 자바스크립트로 작성하기
타입스크립트 코드를 특정 버전의 자바스크립트로 컴파일 할 수 있다. 즉 타입스크립트를 자바스크립트 트랜스 파일러로 사용할 수 있다. 옛날 버전의 자바스크립트 코드를 타입스크립트 컴파일러에서 동작하게 만들면 그 이후 회신 버전의 자바스크립트 기능을 코드에 추가해도 문제가 없다. 이렇게 옛날 버전의 자바스크립트 코드를 최신 버전의 자바스크립트로 바꾸는 작업이 타입스크립트로 전환하는 작업의 일부라 볼 수 있다. 타입스크립트는 자바스크립트의 상위 집합이기 때문에 코드를 최신 버전으로 바꾸다 보면 타입스크립트의 일부를 저절로 익힐 수 있다. 타입스크립트 도입 시 가장 중요하나 기능은 ECMAScript 모듈과 ES2015 클래스이다.
ECMA 모듈 사용하기
ES2015부터 import, export를 사용하는 ECMAScript 모듈이 표준이 되었다. 따라서 자바스크립트 코드가 단일 파일이거나 비표준 모듈 시스템을 사용 중이라면 ES 모듈로 전환해야 한다. 이 과정에서 웹팩이나 ts-node와 같은 도구가 필요할 수도 있다. ES 모듈 시스템은 모듈 단위로 전환할 수 있게 해주기 때문에 점진적 마이그레이션이 원활해진다.
프로토타입 대신 클래스 사용하기
자바스크립트에서 프로토타입 기반의 객체 모델을 사용하는데, 많은 개발자들이 클래스 기반 모델을 선호했기 때문에 ES2015에서 class 키워드를 사용하는 클래스 기반 모델이 도입되었다. 만약 단순한 객체를 다룰 때 프로토타입을 사용하고 있었다면 클래스로 바꾸는 것이 좋다.
var 대신 let/const 사용하기
var 키워드에는 스코프 규칙의 문제가 있었고, let이나 const를 쓰는 것으로 이러한 문제를 피할 수 있었다. var로 되어있던 코드를 let이나 const로 수정하면 일부 코드에서 타입스크립트가 오류를 표시할 수 있다. 만약 오류가 발생했다면 잠재적으로 스코프 문제가 존재하는 코드이니 수정이 필요하다.
for(;;) 대신 for-of 또는 배열 메서드 사용하기
과거에는 자바스크립트가 배열을 순회할 때 for 루프를 사용했는데 모던 자바스크립트에는 for~of
가 존재하기 때문에 이를 사용하면 된다. for~of가 for문에 비해 코드가 짧고 인덱스 변수를 사용하지 않기 때문에 실수를 줄일 수 있다. 만약 인덱스 변수가 필요하다면 forEach를 사용하면 되고, 비슷한 루프문으로 for~in도 있지만 몇 가지 문제점이 있으므로 쓰지 않는 것이 좋다.
함수 표현식보다 화살표 함수 사용하기
this 키워드는 일반 변수들과 다른 스코프 규칙을 가지기 때문에 가장 어려운 개념 중 하나이다. 그러나 화살표 함수를 사용하면 상위 스코프의 this를 유지할 수 있다. 컴파일러 옵션에 noImplicitThis(또는 strict)를 설정하면 타입스크립트가 this 바인딩 관련된 오류를 표시해준다.
단축 객체 표현과 구조 분해 할당 사용하기
1 | const x = 1, |
위와 같은 코드는 아래와 같이 구조 분해 할당을 이용하여 더 간단하게 표현할 수 있다.
1 | const x = 1, |
함수 매개변수 기본값 사용하기
자바스크립트의 함수의 모든 매개변수는 선택적(생략 가능)이며, 매개변수를 지정하지 않았을 때는 undefined로 간주된다. 매개변수에 기본값을 직접 지정할 수도 있는데 이렇게 하면 코드가 간결해진다.
저수준 프로미스나 콜백 대신 async/await 사용하기
async/await가 코드의 가독성을 높이는 것 뿐 아니라 비동기 코드에 타입 정보가 전달되어 타입 추론을 가능하게 한다.
연관 배열에 객체 대신 Map과 Set 사용하기
타입스크립트에 use strict 넣지 않기
버그가 될 수 있는 코드 패턴에 오류를 표시해 주는 엄격 모드가 ES5에서 도입되었는데 타입스크립트에서 수행되는 안정성 검사가 엄격 모드보다 훨싼 더 엄격한 체크를 수행한다.
아이템 59 타입스크립트 도입 전에 @ts-check와 JSDoc으로 시험해 보기
@ts-check 지시자를 써서 타입 체커가 파일을 분석하고, 발견된 오류를 보고하도록 지시할 수 있다. 단 매우 느슨한 수준으로 타입 체크를 수행하고, noImplicitAny를 해제한 것보다 헐거운 체크를 수행한다. JSDoc의 @types 구문을 사용해서 타입 단언을 대체할 수 있는데 이때 중요한 것은 타입을 감싸는 중괄호가 필요하다는 것이다. 이처럼 자바스크립트 환경에서도 @ts-check 지시자와 JSDoc 주석으로 타입스크립트와 비슷한 경험의 작업이 가능하다.
아이템 60 allowJs로 타입스크립트와 자바스크립트 같이 사용하기
마이그레이션 기간 중에 자바스크립트와 타입스크립트가 동시에 동작할 수 있도록 해야 한다. 이 두 가지가 공존할 수 있도록 하는 핵심은 allowJs
컴파일러 옵션이다. 타입스크립트 파일과 자바스크립트 파일을 서로 임포트할 수 있게 해준다. 타입 체크와 관련이 없지만 기존 빌드 과정에 타입스크립트 컴파일러를 추가하기 위해서도 allowJs 옵션이 필요하고, 모듈 단위로 타입스크립트로 전환하는 과정에서 테스트를 수행해야 하기 때문에도 이 옵션이 필요하다.
아이템 61 의존성 관계에 따라 모듈 단위로 전환하기
점진적 마이그레이션에서는 모듈 단위로 각개격파하는 것이 이상적인데, 한 모듈을 골라서 타입 정보를 추가하면 해당 모듈이 의존하는 모듈에서 비롯되는 타입 오류가 발생하게 된다. 따라서 다른 모듈에 의존하지 않는 최하단 모듈부터 시작하여 의존성의 최상단에 있는 모듈을 마지막으로 완성해야 한다.
- 서드파티 라이브러리 타입 정보를 가장 먼저 해결한다.
프로젝트 내에 존재하는 모듈은 서드파티 라이브러리에 의존하지만 서드파티 라이브러리는 해당 모듈에 의존하지 않기 때문에 가장 먼저 타입 정보를 해결해야 한다. @types
모듈을 설치하면 된다. (e.g. @types/lodash)
- 외부 API의 타입 정보 추가
프로젝트 내의 모듈은 API에 의존하지만 API는 해당 모듈에 의존하지 않기 때문에 먼저 해결한다. 단 특별한 문맥이 없어서 타입스크립트가 추론하기 어렵기 때문에 API에 대한 사양을 기반으로 타입 정보를 생성하도록 한다.
의존성 관계도에서 가장 의존성이 낮은 모듈부터 타입 정보 추가
대부분의 프로젝트 최하단에 유틸리티 종류의 모듈이 위치하는 패턴이 있다. 이때 중요한 것은 타입정보를 추가한 것이지 리팩토링을 하려 해서는 안된다.선언되지 않은 클래스 멤버
자바스크립트에서 클래스 멤버 변수를 선언할 필요가 없지만 타입스크립트에서는 명시적으로 선언해야 한다.
- 타입이 바뀌는 값
자바스크립트일 때 문제가 없다가 타입스크립트가 되는 순간 오류가 발생하는 값들이 많은데, 한꺼번에 객체를 생성하여 간단히 오류를 해결할 수 있다. 한꺼번에 생성이 어렵다면 타입 단언문을 사용할 수 있다. 단, 당장 마이그레이션이 최우선이라 임시 방편으로 사용한 것이지 마이그레이션이 완료된 후에는 적절한 방법으로 타입을 선언해야 한다. 또한 자바스크립트에서 JSDoc와 @ts-check를 사용해서 타입 정보를 추가했다면 타입스크립트로 전환하는 순간 타입 정보가 무효화 된다는 것에 유의하도록 한다.
- 테스트 코드를 타입스크립트로 전환
로직 코드가 테스트 코드에 의존하지 않기 때문에, 테스트 코드는 항상 의존성 관계도의 최상단에 위치한다. 따라서 최하단의 모듈부터 마이그레이션 하는 중에도 테스트는 정상적으로 수행할 수 있다.
아이템 62 마이그레이션의 완성을 위해 noImplicitAny 설정하기
만약 noImplicitAny를 설정하지 않았다면 타입 선언에서 비롯되는 실제 오류를 완벽하게 잡을 수 없으므로 마이그레이션이 완료되었다고 말하기 어렵다. 아래와 같은 코드는 noImplicitAny:true
를 설정한 순간 에러가 발생하게 된다.
1 | // tsConfig: {"noImplicitAny":true,"strictNullChecks":false} |
noImplicitAny를 설정할 때는 로컬에만 설정하고 작업을 하여 점진적 마이그레이션이 가능하도록 한다.
References
[이펙티브 타입스크립트] 댄 밴더캄 지음