아이템53. 타입스크립트 기능보다는 ECMAScript 기능을 사용하기
enum 사용을 최소화 하기
- 문자열
enum
과 숫자형enum
이 다르게 동작합니다. 접근 방식의 차이, JS로 변환되었을 때의 차이가 있습니다.
// .ts
enum Flavor {
VANILLA = "vanilla",
CHOCOLATE = "chocolate",
STRAWBERRY = "strawberry",
}
Flavor.VANILLA; // 접근 가능
console.log(Flavor[0]); // 불가
enum Types {
Circle = 0,
Ellipse = 1,
Polygon = 2,
Line = 3,
}
Types.Circle; // 접근 가능
console.log(Types[0]); // "Circle"
// .js
var Flavor;
(function (Flavor) {
Flavor["VANILLA"] = "vanilla";
Flavor["CHOCOLATE"] = "chocolate";
Flavor["STRAWBERRY"] = "strawberry";
})(Flavor || (Flavor = {}));
Flavor.VANILLA; // 접근 가능
console.log(Flavor[0]); // 불가
var Types;
(function (Types) {
Types[Types["Circle"] = 0] = "Circle";
Types[Types["Ellipse"] = 1] = "Ellipse";
Types[Types["Polygon"] = 2] = "Polygon";
Types[Types["Line"] = 3] = "Line";
})(Types || (Types = {}));
Types.Circle; // 접근 가능
console.log(Types[0]); // "Circle"
}
- JS와 동작이 다릅니다.
getFlavor('vanilla'); // js에서는 가능하지만 ts에서는 불가
- 유니온 타입 사용을 권장합니다.
type Flavor = 'vanilla' | 'chocolate' | 'strawberry';
생성자의 매개변수 사용하지 않기
멤버 변수와 생성자의 매개변수 멤버를 혼합해서 사용할 때 설계가 혼란스러울 수 있습니다.
class Person {
first: string;
last: string;
constructor(public name: string) {
[this.first, this.last] = name.split(' ');
}
}
네임스페이스와 트리플 슬래시 임포트
namespace foo {
function bar() {}
}
/// <reference path='other.ts'>
데코레이터
데코레이터는 아직 비표준 기능이며, 따라서 향후에 호환성이 깨질 가능성이 있습니다. 따라서 사용을 지양해야 합니다.
아이템54. 객체를 순회하는 노하우
const obj = {
one: 'uno',
two: 'dos',
three: 'tres',
};
for (const k in obj) {
const v = obj[k];
// k는 string으로 추론되었지만, obj의 키는 3가지로 제한(one, two, three)
}
// k를 아래와 같이 선언해 주면 키의 범위를 정확하게 선언할 수 있습니다.
let k: keyof typeof obj;
하지만 위와 같이 오류 없이 순위하더라도 구조덕 타이핑을 사용하는 특성 때문에 다른 문제가 발생할 수 있습니다.
interface ABC {
a: string;
b: string;
c: number;
}
function foo(abc: ABC) {
let k: keyof ABC;
for (k in abc) {
const v = abc[k]; // string | number, Date 타입은 무시되었음
}
}
const x = {a: 'a', b: 'b', c: 2, d: new Date()};
foo(x); // 호출 가능
골치 아픈 문제를 피하고 싶다면 Object.entries()
를 사용하면 됩니다.
interface ABC {
a: string;
b: string;
c: number;
}
function foo(abc: ABC) {
for (const [k, v] of Object.entries(abc)) {
k // Type is string
v // Type is any
}
}
아이템55. DOM 계층 구조 이해하기
EventTarget
DOM
타입 중 최상위 타입인 EventTarget
은 addEventListener
, RemoveEventListener
등 밖에 할 수 없습니다. 예제에서 currentTarget은 실제로 HTMLElement
타입이지만 타입관점에서 EventTarget
이 될 수 있습니다.
Node
다음으로 Node
타입은 Element
타입을 포함하지만 Element
가 아닌 Node
도 있습니다. 텍스트 노드와 주석이 그 예입니다.
Element
그 다음으로는 Element
타입이 있습니다. Element
타입에는 HTMLElement
와 SVGElement
가 포함됩니다.
HTMLElement
마지막으로 HTMLElement
는 HTML***Elment
를 포함합니다.
그런데 getElementById
등으로 노드를 엘리먼트를 선택하는 경우, 타입스크립트 보다 개발자가 더 정확하게 어떤 HTML***Element
인지 알 수 있기 때문에 as
로 타입 단언문을 사용해도 됩니다.
document.getElementById('main-div') as HTMLDivElement
아이템56. 정보를 감추는 목적으로 private 사용하지 않기
TS에서 public
, protect
, private
접근 제어자를 제공하는데, 이것은 JS에서도 접근 제어가 되는 것처럼 오인할 수 있도록 하기 때문에 지양해야합니다. 그리고 TS에서 private
을 사용하더라도 타입 단언문을 통해 간단히 접근할 수 있습니다.
class Diary {
private secret = 'cheated on my English test';
}
const diary = new Diary();
(diary as any).secret // OK
JS에서 데이터 은닉을 위해 private
기능을 해시(#)
를 통해 제공하므로 TS에서 해시(#)
을 사용하여 private
를 구현할 수 있습니다. 또는 클로저 기법을 이용해도 동일하게 데이터 은닉을 할 수 있습니다.
아이템57. 소스맵을 사용하여 타입스크립트 디버깅하기
TS -> JS로 트랜스파일링 되면서 사실상 디버깅이 거의 불가능합니다. 이에 브라우저 제조사들이 source map
이라는 것을 지원하게 되었습니다. tsconfig
에 아래와 같이 설정하고 컴파일을 하면 *.js.map 이라는 파일이 생기는데, 실행 시에 브라우저의 디버거에서 index.ts
라는 중간 파일이 생성되며 이 파일을 통해서 break point
를 설정하거나 변수를 조사 할 수 있게 됩니다.
// tsconfig.json
{
"compilerOptions": {
"sourceMap": true
}
}
source map
을 적용하여 컴파일 후 vscode 디버거
를 통해서도 디버깅이 가능합니다.