본문으로 건너뛰기

DOM 요소의 타입

타입스크립트 목차모듈·선언·설정레슨 46 / 51

목차

브라우저에서 document.querySelector("#qty")⁠로 찾은 요소의 타입은 타입스크립트 기본 선언 파일(lib.dom.d.ts)에 적힌 대로 Element | null입니다. 요소를 못 찾으면 null일 수 있고(TS18047), 찾았더라도 일반 Element에는 입력 칸 전용 속성인 value가 없으므로(TS2339), instanceof HTMLInputElement⁠로 좁힌 뒤에야 input.value를 안전하게 읽을 수 있습니다.

주문 수량 입력 칸(#qty)에서 값을 꺼내는 브라우저 코드를 타입 검사기로 점검하면서, 왜 두 가지 오류가 동시에 나는지와 instanceof로 한 번에 좁히는 법을 살펴봅니다.

JS 다시 보기: 요소 찾기: querySelector, 폼 제출과 입력 값에서 querySelector로 요소를 찾고 입력 칸의 .value를 읽는 법을 다뤘습니다.

5단계 중 1단계. 1~2번 줄: document.querySelector("#qty")가 돌려주는 타입은 기본 선언 파일(lib.dom.d.ts)에 적힌 대로 Element | null입니다.

1왜 querySelector 뒤에서 바로 .value를 못 읽을까?

방금 앞 레슨에서 배운 선언 파일(.d.ts) 덕분에, 타입스크립트는 브라우저 전역 객체 document와 태그별 클래스(Element, HTMLInputElement, HTMLButtonElement)의 모양을 기본 선언 파일(lib.dom.d.ts)로 이미 알고 있습니다.

그런데 document.querySelector("#qty")⁠는 넘긴 문자열("#qty")만 보고서는 그 요소가 화면에 진짜 있는지, 그리고 <input>인지 <div>인지 알 수 없습니다. 그래서 반환 타입이 Element | null로 정해져 있습니다.

확인 없이 바로 input.value를 읽으면 한 줄에서 두 가지 오류가 함께 납니다. (브라우저 전용 document 코드이므로 타입 검사 결과만 확인합니다.)

const input = document
  .querySelector("#qty");
console.log(input.value);
검사 결과 오류 2개
main.ts(3,13): error TS18047: 'input' is possibly 'null'.main.ts(3,19): error TS2339: Property 'value' does not exist on type 'Element'.
  • TS18047: 화면에 #qty 요소가 없으면 input이 null일 수 있습니다.
  • TS2339: null이 아니더라도 모든 태그의 공통 부모인 Element에는 value 속성이 없습니다.

2instanceof HTMLInputElement⁠로 한 번에 좁히기

if 조건에서 instanceof HTMLInputElement⁠로 확인하면 두 문제가 한꺼번에 풀립니다.

  1. null은 HTMLInputElement의 인스턴스가 아니므로 null 가능성이 걸러집니다.
  2. 일반 Element 가운데서도 value: string 속성을 가진 HTMLInputElement임이 확인됩니다.
const input = document
  .querySelector("#qty");

if (
  input instanceof
  HTMLInputElement
) {
  console.log(input.value);
}
검사 결과 오류 없음

검사 오류가 0이 되어 아무 메시지도 나오지 않습니다. 갈래 안에서 input의 지금 타입은 HTMLInputElement이고, input.value의 타입은 string입니다.

찾으려는 요소좁힐 때 쓰는 클래스주요 고유 속성
<input> 입력 칸HTMLInputElementvalue: string, checked: boolean
<button> 버튼HTMLButtonElementdisabled: boolean
<form> 폼HTMLFormElementreset(): void, submit(): void

예제

검사 결과를 먼저 적어 본 뒤 펼쳐 보세요. 맞혀 보기는 점수에 들지 않습니다.

주문 버튼(#order-btn)을 찾아 HTMLButtonElement로 좁힌 뒤 disabled 속성을 true로 바꿉니다.

const btn = document
  .querySelector("#order-btn");

if (
  btn instanceof
  HTMLButtonElement
) {
  btn.disabled = true;
}

검사를 지나갈까요? 지나가지 못하면 몇 번 줄에서 막힐까요?

검사 결과 보기
검사 결과 오류 없음

instanceof HTMLButtonElement 갈래 안에서 btn은 HTMLButtonElement로 좁혀지므로, 일반 Element에는 없는 disabled: boolean 속성에 true를 안전하게 대입할 수 있습니다.

자주 틀리는 곳

틀린 생각: if (input !== null)로 null만 거르면 바로 input.value를 쓸 수 있다.

if (input !== null)로 null만 거르면 TS18047은 사라지지만, input의 지금 타입이 여전히 Element여서 TS2339 오류가 그대로 남습니다.

const input = document
  .querySelector("#qty");

if (input !== null) {
  console.log(input.value);
}
검사 결과 오류 1개
main.ts(5,21): error TS2339: Property 'value' does not exist on type 'Element'.

맞는 규칙: <input>의 value를 읽으려면 input !== null 대신 instanceof HTMLInputElement⁠로 좁혀야 합니다.

틀린 생각: as HTMLInputElement나 !를 붙여서 빨간 줄만 지우면 안전하다.

귀찮다고 as HTMLInputElement로 단언하거나 input!를 붙이면 검사 오류는 사라집니다(단언이 값을 바꾼다는 생각). 하지만 HTML에서 #qty의 오타가 나면 실행 때 null.value를 읽다가 화면이 멈추고, 만약 #qty가 <div> 태그라면 input.value가 조용히 undefined가 됩니다.

맞는 규칙: DOM 요소는 단언(as)으로 덮지 않고 instanceof 조건문으로 실제 요소를 확인해 좁힙니다.

틀린 생각: <input type="number">⁠에서 읽은 input.value의 타입은 number이다.

HTML에 <input type="number">⁠라고 적었더라도 자바스크립트에서 읽는 input.value의 타입은 언제나 **string**입니다.

const input = document
  .querySelector("#qty");

if (
  input instanceof
  HTMLInputElement
) {
  const total =
    input.value * 4500;
  console.log(total);
}
검사 결과 오류 1개
main.ts(9,5): error TS2362: The left-hand side of an arithmetic operation must be of type 'any', 'number', 'bigint' or an enum type.

맞는 규칙: input.value는 항상 string이므로, 산술 곱셈에 쓰려면 Number(input.value)로 바꾸어 계산합니다.

확인 문제

1. document.querySelector("#qty")⁠로 찾은 input에서 바로 input.value를 읽으면 왜 오류가 두 개 나나요?

힌트

querySelector의 반환 타입인 Element | null의 두 갈래를 각각 생각해 보세요.

정답

첫째로 요소를 못 찾으면 input이 null일 수 있고(TS18047), 둘째로 찾았더라도 Element 타입에는 value 속성이 없기 때문입니다(TS2339).

2. if (input !== null) 대신 instanceof HTMLInputElement⁠로 확인하면 어떤 점이 더 좋은가요?

힌트

null을 거른 뒤 남는 타입이 Element인지 HTMLInputElement인지 비교해 보세요.

정답

null을 거르는 것과 동시에 Element를 HTMLInputElement로 좁혀 주어 input.value 속성을 검사 오류 없이 안전하게 읽을 수 있습니다.

3. HTMLInputElement로 좁힌 뒤 읽은 input.value의 타입은 무엇인가요?

힌트

산술 곱셈(input.value * 4500)에서 왜 TS2362가 났는지 떠올려 보세요.

정답

항상 string입니다. 숫자 입력 칸이더라도 .value는 문자열이므로 수 계산을 하려면 Number(input.value)로 변환해야 합니다.

컴퓨터에서 해 보기(선택) — 점수에 들지 않습니다. 폰으로만 보고 있다면 건너뛰세요. 2절의 코드를 main.ts로 저장하고 npx tsc로 검사해 오류가 0인지 확인한 뒤, if 줄을 지웠을 때 TS18047과 TS2339 두 줄이 나오는 모습을 직접 확인해 보세요. 다음 레슨 tsconfig.json 읽기에서는 지금까지 써 온 설정 파일의 각 줄을 읽습니다. 검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

타입스크립트 목차로