📌 TypeScript로 HTML 요소 조작하기

1
2
3
4
5
6
// index.ts
const title = document.getElementById("title");

if (title instanceof HTMLElement) {
title.innerHTML = "제목입니다.";
}

❗️ title 요소가 null 타입일 수 도 있다.라는 오류!

자바스크립트에서 HTML 요소를 타입 좁히기 없이 조작할 수 있었지만, TypeScript에서는 타입을 확실하게 지정해줘야지만 오류를 발생시키지 않는다.

위와 같이 타입을 좁히는 과정을 Narrowing이라고 한다.

✈️ narrowing 방법

1. HTML 구분 명확히

1
2
3
4
5
const img = document.getElementById("image");

if (img instanceof HTMLImageElement) {
img.src = "new.jpg";
}
  • HTMLImageElement 말고 그냥 HTMLElement로 검사를 하게될 경우 광범위한 타입에 대한 문제와 HTMLElement 에는 src 속성이 없으므로 명확한 타입으로 narrowing 해줘야한다.

2. instance of 연산자

1
2
3
4
5
const title = document.getElementById("title");

if (title instanceof HTMLElement) {
title.innerHTML = "제목입니다.";
}

3. optional chaining 연산자

1
2
3
4
5
const button = document.getElementById("button");

button?.addEventListener("click", () => {
console.log("clicked!");
});
  • button 요소가 null이거나 undefined 이면 undefined를 반환하고 그렇지 않으면 이벤트 리스너를 등록한다.

4. 명시적 태그 사용(tagged union)

1
2
3
4
5
6
7
8
9
10
interface UploadEvent {
type: "upload";
fileName: string;
contents: string;
}
interface DownloadEvent {
type: "download";
fileName: string;
contents: string;
}

5. let 대신 const 키워드로 선언

  • 만능은 아니다. 너무 많은 후보들이 있으면 곤란하다.
1
2
3
4
5
6
7
8
const mixed = ["x", 1];

// ('x'|1)[]
// ['x', 1]
// [string, number]
// readonly [string, number]
// (string|number)[]
// readonly (string|number)[]

6. if 구문 사용

1
2
3
4
5
6
7
8
9
const el = document.getElementById("foo"); // type is HTMLElement | null

if (el) {
el; // type is HTMLElement
el.innerHTML = "party time".blink();
} else {
el; // type is null
alert("No element #foo");
}

7. in 연산자 사용

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface A {
a: number;
}
interface B {
b: number;
}
function pickAB(ab: A | B) {
if ("a" in ab) {
ab; // type is A
} else {
ab; // type is B
}
ab; // type is A | B
}