해당 포스트는
이펙티브 타입스크립트
7장을 읽고 정리한 포스트입니다.
책의 모든 내용을 작성하는 것이 아닌 주관적인 기준에 따라 필요한 정보만 정리했습니다.
📖 7장 코드를 작성하고 실행하기
초기 JavaScript
에 없던 기능들을 TypeScript
에서 독립적으로 만들었고, 이후에 JavaScript
에서 다른 방식으로 표준이 돼서 호환성에 문제가 생기는 부분들에 대해 알아보는 챕터입니다.
TypeScript
는 JavaScript
의 호환성을 포기했기 때문에 발생하는 혼란스러운 기능들에 대해 알아보겠습니다.
📌 Item 53 ( 열거형(enum) )
- 특징
- 숫자 열거형은
0
부터 시작하는 것이 좋음 ( TODO: 이유 알아보기 ) - 상수 열거형(
const eum
)은 런타임에 완전히 제거됨
(preserveConstEnums
이true
인 경우 상수 열거형의 정보 저장 ) - 문자형 열거형은 명목적 타이핑을 사용함
- 숫자 열거형은
( 근데 이상하게 tsconfig.json
에서 preserveConstEnums
를 설정하면 안되고 --preserveConstEnums
를 사용하면 되네요… 🥲 )
( npx tsc 파일명
과 같은 명령어를 실행하면 tsconfig.json
을 사용하지 않는다고 합니다… ( npx tsc
사용하기 ) )
0️⃣ 상수 열거형 ( const enum )
일반적으로 상수 열거형은 런타임에 제거됩니다.
단, preserveConstEnums
를 적용하면 const enum
도 런타임에 제거되지 않습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 상수 열거형
const enum Flavor {
VANILLA,
CHOCOLATE,
STRAWBERRY,
}
let flavor = Flavor.CHOCOLATE;
// 일반 열거형
enum Flavor {
VANILLA,
CHOCOLATE,
STRAWBERRY,
}
let flavor = Flavor.CHOCOLATE;
1
2
3
4
5
6
7
8
9
10
11
// 상수 열거형 ( 컴파일 )
var flavor = 1 /* Flavor.CHOCOLATE */;
// 일반 열거형 ( 컴파일 )
var Flavor;
(function (Flavor) {
Flavor[Flavor["VANILLA"] = 0] = "VANILLA";
Flavor[Flavor["CHOCOLATE"] = 1] = "CHOCOLATE";
Flavor[Flavor["STRAWBERRY"] = 2] = "STRAWBERRY";
})(Flavor || (Flavor = {}));
var flavor = Flavor.CHOCOLATE;
1️⃣ 문자형 열거형과 명목적 타이핑
enum
은 구조적 타이핑이 아닌 명목정 타이핑을 따르기 때문에 (1)
에서 에러가 발생하게 됩니다.
따라서 교재에서는 enum
을 문자로 사용하지 않는 것을 권장합니다.
1
2
3
4
5
6
7
8
9
10
enum Flavor {
VANILLA = "VANILLA",
CHOCOLATE = "CHOCOLATE",
STRAWBERRY = "STRAWBERRY",
}
const func = (flavor: Flavor) => flavor;
func(Flavor.CHOCOLATE);
func("CHOCOLATE"); // (1) '"CHOCOLATE"' 형식의 인수는 'Flavor' 형식의 매개 변수에 할당될 수 없습니다.
리터럴 타입의 유니온을 이용해서 대신 처리할 수 있습니다.
1
2
3
4
5
type Flavor = "VANILLA" | "CHOCOLATE" | "STRAWBERRY";
const func = (flavor: Flavor) => flavor;
func("CHOCOLATE"); // 타입 추론이 되기 때문에 자동완성도 지원해줌
2️⃣ 매개변수 속성
(2)
와 (3)
모두 같은 방식으로 동작합니다.
( (3)
처럼 사용하는 것이 매개변수 속성 방식입니다. )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// (2)
class Person1 {
name: string;
constructor(name: string) {
this.name = name;
}
}
// (3)
class Person2 {
constructor(public name: string) {
this.name = name;
}
}
- 매개변수 속성의 문제점
- 컴파일 시 코드가 늘어남
- 일반 속성과 섞어서 사용하면 혼란이 발생
제가 느끼기엔 생소하고 굳이 사용해야 할 필요성이 느껴지지 않아서 사용하지 않을 것 같습니다.
3️⃣ 네임스페이스와 트리플 슬래시 임포트
TODO:
4️⃣ 데코레이터
TODO:
🎊 Item 53 결론
- 일반적인
TypeScript
코드에서 모든 타입 정보를 제거하면JavaScript
가 되지만, 열거형, 매개변수 속성, 트리플 슬래시 임포트, 데코레이터는 타입 정보를 제거한다고JavaScript
가 되지 않음 - 열거형, 매개변수 속성, 트리플 슬래시 임포트, 데코레이터를 사용하지 않는 것이 좋음
📌 Item 54 ( 객체를 순회하는 노하우 )
해당 아이템에서 아래 코드를 모두 사용한다고 가정하고 설명하겠습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
type Person = {
name: string;
age: number;
gender: boolean;
};
declare const person: Person;
const strangePerson = {
age: 1,
gender: true,
name: "alice",
birth: new Date(),
};
0️⃣ for ~ in 으로 객체 순회하는 방법
일반적으로 아래와 같이 작성하면 오류가 나는데 처음 만나면 이게 왜 오류인지 이해할 수 없을 수 있습니다.
1
2
3
4
5
6
const func = (person: Person) => {
let key: keyof typeof person;
for (key in person) {
console.log(person[key]); // 'string' 형식의 식을 'Person' 인덱스 형식에 사용할 수 없으므로 요소에 암시적으로 'any' 형식이 있습니다.
}
};
person
이라는 객체는 const
이기는 하지만 언제든지 property
가 추가될 수 있기 때문에 key
는 string
으로 추론됩니다.
또한 TypeScript
는 구조적 타이핑을 따르기 때문에 Person
에 정해진 key
만 들어갈 것이라고 확신할 수 없습니다.
( 구조적 타이핑을 따르기 때문에 (1)
과 같은 경우도 가능합니다. )
1
2
3
4
5
6
7
8
9
const func = (person: Person) => {
let key: keyof typeof person;
for (key in person) {
console.log(person[key]);
}
};
func(myPerson);
func(strangePerson); // (1)
(2)
처럼 key
의 타입을 명시적으로 작성하기 때문에 오류가 나지 않습니다.
혹은 (3)
처럼 Object.keys()
, Object.values()
, Object.entries()
를 사용해도 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const func1 = (person: Person) => {
let key: keyof typeof person; // (2)
for (key in person) {
// v: string | number | boolean
const v = person[key];
}
};
const func2 = (person: Person) => {
// (3)
for (const [key, value] of Object.entries(person)) {
// key: string
// value: string | number | boolean
}
};
func1(myPerson);
func2(strangePerson);
🎊 Item 54 결론
- 함수의 매개변수로 쓰이는 객체에는
property
가 추가될 수 있음을 인지해야 함 - 객체를 순회할 때는
Object.keys()
,Object.values()
,Object.entries()
를 사용하는 것이 일반적임
📌 Item 55 ( DOM 계층 구조 이해하기 )
0️⃣ DOM 계층 타입
EventTarget
:window
,XMLHttpRequest
Node
:document
,Text
,Comment
Element
:HTMLElement
,SVGElement
HTMLElement
:<i>
,<b>
등HTML*Element
:<button>
,<div>
등
Node
의 Text
나 Comment
는 텍스트 노드, 주석 노드를 의미합니다.
1
2
3
4
5
6
7
8
<html>
<head></head>
<body>
<!-- 주석 노드 -->
<p>🙂 텍스트 노드 🙂</p>
</body>
</html>
1️⃣ Event 타입
UIEvent
: 모든 종류의 사용자 인터페이스 이벤트MouseEvent
: 클릭처럼 마우스로부터 발생되는 이벤트TouchEvent
: 모바일 기기의 터치 이벤트WheelEvent
: 스크롤 휠을 돌려서 발생되는 이벤트KeyboardEvent
: 키 누름 이벤트
이벤트 핸들러에서 리터럴로 선언하면 이벤트 타입을 정의하지 않아도 추론해줍니다.
하지만 따로 빼서 작성하는 경우는 이벤트 타입을 명시적으로 선언해야 합니다.
1
2
3
4
5
6
7
8
9
10
const clickHanlder = (e: MouseEvent) => {
// 명시적으로 작성
// e: MouseEvent
};
document.createElement("button").addEventListener("click", (e) => {
// 알아서 추론
// e: MouseEvent
});
document.createElement("button").addEventListener("click", clickHanlder);
2️⃣ DOM의 Element 가져오기
DOM
의 Element
를 가져오는 경우에는 타입이 명확하지 않은 경우가 더 많습니다.
직접 생성하는 경우는 명확하지만 가져오는 경우에는 TypeScript
에서는 타입을 확신할 수 없습니다.
1
2
3
4
5
// $$button: HTMLButtonElement
const $$button = document.createElement("button");
// $button: HTMLButtonElement | null
const $button = document.querySelector("button");
저 같은 경우에는 타입 가드(instanceof
)를 이용해서 타입 체커에게 타입을 알려주는 방법을 사용합니다.
1
2
3
4
5
6
7
8
9
(() => {
const $button = document.querySelector("button");
// 타입 가드
if (!($button instanceof HTMLButtonElement))
return alert("<button>이 존재하지 않습니다.");
// $button: HTMLButtonElement
})();
🎊 Item 55 결론
DOM
의 타입 계층에 대해 공부하고 이해하기Node
,Element
,HTMLElement
,EventTarget
간의 차이점과Event
,MouseEvent
등의 차이점을 알아야 함DOM
의 엘리먼트나 이벤트에 대해 추론할 수 있도록 문맥 정보를 활용해야 함
📌 Item 56 ( 정보를 감추는 목적으로 private 사용하지 않기 )
TypeScript
에서는 접근 제어자를 이용해서 멤버 변수의 접근에 제한을 걸 수 있습니다.
( 접근 제어자는 public
, protected
, private
이 있습니다. )
0️⃣ 접근 제어자
TypeScript
의 접근 제어자는 컴파일 후에 모두 제거됩니다.
JavaScript
에는 접근 제어자라는 문법이 존재하지 않기 때문입니다.
( TypeScript
의 문법이 먼저 나오고 JavaScript
의 문법이 다른 방법으로 표준으로 나와서 서로 맞지 않음 )
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// ts
class Person {
private name: string;
protected age: number;
public gender: boolean;
constructor(name: string, age: number, gender: boolean) {
this.name = name;
this.age = age;
this.gender = gender;
}
}
const person = new Person("Aatrox");
person.name;
// js
var Person = /** @class */ (function () {
function Person(name, age, gender) {
this.name = name;
this.age = age;
this.gender = gender;
}
return Person;
}());
var person = new Person("Aatrox");
person.name; // 일반적인 멤버이기 때문에 문제 없이 동작
1️⃣ 정보 감추는 방법
아래처럼 private
는 타입 체커에 의해서만 즉, 컴파일 타임에서만 체크가 됩니다.
따라서 강제로 타입을 변환하면 접근할 수 있게 됩니다.
1
2
3
4
5
6
7
8
9
10
11
12
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
}
const person = new Person("Aatrox");
person.name; // Error: 'name' 속성은 private이며 'Person' 클래스 내에서만 액세스할 수 있습니다.
(person as any).name; // 정상 동작
이런 문제를 해결하기 위해 JavaScript
의 비공개 필드 기능(#
)을 이용할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
say() {
console.log(this.#name);
}
}
const person = new Person("Aatrox");
person.#name; // Error: '#name' 속성은 프라이빗 식별자를 포함하기 때문에 'Person' 클래스 외부에서 액세스할 수 없습니다.
(person as any).name; // 정상 동작
person.say();
// ts ( ... 나머지 생략 )
// 변환해도 결국 잘못된 접근이라 "JavaScript"에서 오류가 납니다.
person.;
person.name;
person.say();
🎊 Item 56 결론
- 접근 제한자는
TypeScript
에서만 존재하고 강제적인 조건이 부여될 뿐이고,JavaScript
에는 존재하지 않음 - 데이터를 감추려면 클로저나 비공개 필드 기능을 이용하기
📌 Item 57 ( 소스맵을 사용하여 타입스크립트 디버깅하기 )
소스맵이란 원본 코드와 변환 코드를 맵핑해주는 역할을 하는 것을 의미합니다.
디버깅을 위해서 사용하며 브라우저와 IDE
에서 소스맵을 해석해줍니다.
소스맵 때문에 네트워크 비용이 추가로 발생할 수 있다고 생각할 수 있지만 브라우저의 디버거를 열기 전에는 소스맵을 받지 않기 때문에 문제가 되지 않습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// index.ts
const age: number = 26;
console.log(age);
// index.js ( "sourceMap": true )
"use strict";
const age = 26;
console.log(age);
// + index.js.map 생성됨
// index.js ( "inlineSourceMap": true )
"use strict";
const age = 26;
console.log(age);
//# sourceMappingURL=data:application/json;base64, ... 생략
🎊 Item 57 결론
- 변환된 코드 디버깅하지 말고 소스맵 사용하기
- 소스맵 공개하지 않기
📮 레퍼런스
- « 이펙티브 타입스크립트 7장 » ( 댄 밴더캄 지음, 장원호 옮김, 인사이트, 2021 )
- 1-blue - 구조적 타이핑
- 1-blue - 타입 가드(
instanceof
)