
목표: 코드를 작성하고 실행하면서 실제로 겪을 수 있는 문제를 알아보자
아이템53 타입스크립트의 기능보다는 ECMAScript 기능을 사용하기과거 자바스크립트의 부족한 점을 타입스크립트는 독립적으로 개발해 보완했다.
시간이 흐르며 자바스크립트는 내장 기능을 추가했고, 이는 타입 공간과 값 공간의 경계를 혼랍스럽게 한다.
어떤 점을 유의해야 호환성 문제를 일으키지 않는지 알아보자.
타입스크립트의 enum
enum Flavor {
VANILLA = 0,
CHOCOLATE = 1,
STRAWBERRY = 2,
}
타입스크립트 enum의 문제
문자열 열거형의 명목적 타이핑 알아보기
자바스크립트와 타입스크립트의 동작이 다르다
enum Flavor {
VANILLA = 'vanilla',
CHOCOLATE = 'chocolate',
STRAWBERRY = 'strawberry',
}
let flavor = Flavor.CHOCOLATE; // 타입이 Flavor
flavor = 'strawberry';
// ~~~~~~ Type '"strawberry"' is not assignable to type 'Flavor'
// 명목적 타이핑으로 인해 할당이 되지 않는다?
function scoop(flavor: Flavor) {
/* ... */
}
scoop('vanilla'); // 'vanilla' 형식은 'Flavor' 형식의 매개변수에 할당될 수 없습니다.
scoop(Flavor.VANILLA); // 정상
따라서 문자열 열거형 대신 리터럴 유니온 타입을 사용하자
type Flavor = 'vanilla' | 'chocolate' | 'strawberry';
let flavor: Flavor = 'chocolate'; // OK
아래 두 코드는 동일한 기능을 한다
// 생성자 매개변수
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
}
// 매개변수 속성
class Person {
constructor(public name: string) {}
}
일관성 없이 쓰면 혼란스럽다
// BAD
class Person {
first: string;
last: string;
constructor(public name: string) {
[this.first, this.last] = name.split(' ');
}
}
클래스에 매개변수 속성만 존재한다면 클래스 대신 인터페이스로 만들고 객체 리터럴을 사용하는 것이 좋습니다.
class Person {
constructor(public name: string) {}
}
const p: Person = { name: 'Jed Bartlet' }; // OK
// 위처럼 매개변수 속성만 존재하는 경우에는 인터페이스로 만들자는 뜻 같습니다?
매개변수 속성과 일반 속성을 같이 쓰지 말고 한 가지만 사용하자!
타입스크립트는 자체적인 모듈 시스템으로 module 키워드와 트리플 슬래시 임포트를 사용했다.
ECMAScript 2015가 공식적으로 모듈 시스템을 도입한 이후에는 module과 같은 기능을 하는 namespace 키워드를 추가했다.
트리플 슬래시와 module은 호환성을 위해 남겨져 있다. import와 export를 사용하자
앵귤러 프레임워크나 애너테이션이 필요한 프레임워크를 사용하는 것이 아니라면 데코레이터는 사용하지 말자
아이템54 객체를 순회하는 노하우interface ABC {
a: string;
b: string;
c: number;
}
function foo(abc: ABC) {
for (const k in abc) {
const v = abc[k];
// ~~~~~~ 'ABC' 타입에 인덱스 시그니처가 없기 때문에
// 엘리먼트는 암시적으로 'any'가 됩니다.
}
}
foo({ a: 'a', b: 'b', c: 2, d: new Date() }); // OK
// keyof 키워드를 사용하면 문제점
function foo(abc: ABC) {
let k: keyof ABC
for (k in abc) {
// let k: "a" | "b" | "c"
const v = abc[k] // Type is string | number
// '할당 가능한' 모든 값에 비해 타입이 너무 좁게 추론된다.
}
// 아래처럼 순회하자!
function foo(abc: ABC) {
for (const [k, v] of Object.entries(abc)) {
k // Type is string
v // Type is any
}
}
Object.entries를 사용하는 것이다아이템55 DOM 계층 구조 이해하기HTMLParagraphElement < HTMLElement < Element < Node < EventTarget
계층구조에 따른 예시
<i>, <b><button>구체적으로 DOM 관련 타입을 지정해야 오류 없이 속성에 접근할 수 있다.
리터럴 값을 사용하면 정확한 타입을 얻을 수 있다.
document.getElementsByTagName('p')[0]; // HTMLParagraphElement
document.createElement('button'); // HTMLButtonElement
document.querySelector('div'); // HTMLDivElement
우리가 타입스크립트보다 더 정확히 알고있는 경우 단언문을 사용해도 좋다
document.getElementById('my-div') as HTMLDivElement;
Event 타입도 별도의 계층구조가 있으면 Event는 가장 추상화된 이벤트이다.
// clientX, clientY는 MouseEvent 타입에 있다..
function handleDrag(eDown: Event) {
// ...
const dragStart = [eDown.clientX, eDown.clientY];
// ~~~~~~~ Property 'clientX' does not exist on 'Event'
// ~~~~~~~ Property 'clientY' does not exist on 'Event'
// ...
}
아이템56 정보를 감추는 목적으로 private 사용하지 않기런타임에서 동작하지 않으므로 우회해 접근할 수 있다 => 데이터가 감춰지지 않는다
class Diary {
private secret = 'cheated on my English test';
}
const diary = new Diary();
diary.secret; // 접근 불가능
(diary as any).secret; // 단언을 통해 우회하여 접근이 가능
외부에서 passwordHash에 접근이 불가능하다
declare function hash(text: string): number;
class PasswordChecker_ {
checkPassword: (password: string) => boolean;
constructor(passwordHash: number) {
this.checkPassword = (password: string) => {
return hash(password) === passwordHash;
};
}
}
const checker = new PasswordChecker(hash('s3cret'));
checker.checkPassword('s3cret'); // Returns true
생성자 내부에서 메서드를 정의할 경우 인스턴스를 생성할 때마다 메서드의 복사본이 생성되므로 메모리가 낭비된다.
또한 동일한 클래스 인스턴스끼리 비공개 데이터에 접근이 불가능하다.
메모리 낭비 X
동일한 클래스 인스턴스끼리 비공개 데이터에 접근 O
declare function hash(text: string): number;
class PasswordChecker {
#passwordHash: number;
constructor(passwordHash: number) {
this.#passwordHash = passwordHash;
}
checkPassword(password: string) {
return hash(password) === this.#passwordHash;
}
}
아이템57 소스맵을 사용하여 타입스크립트 디버깅하기디버깅은 런타임에 동작하며, 디버깅하면 보게 되는 코드는 전처리기, 컴파일러, 압축기를 거친 자바스크립트 코드일 것이다.
이 코드는 복잡해 디버깅하기 어렵다.
디버깅 문제를 해결하기 위해 브라우저 제조사들은 서로 협력해 소스맵이라는 해결책을 내놓았다.
소스맵은 변환된 코드의 위치와 심벌들을 원본 코드의 원래 위치와 심벌들로 매핑한다.
타입스크립트가 소스맵을 생성할 수 있게 하려면 tsconfig.json에서 sourceMap 옵션을 설정하면 된다.
.js.map 파일이 소스맵이다.