브라우저에서 document.querySelector("#qty")로 찾은 요소의 타입은 타입스크립트 기본 선언 파일(lib.dom.d.ts)에 적힌 대로 Element | null입니다. 요소를 못 찾으면 null일 수 있고(TS18047), 찾았더라도 일반 Element에는 입력 칸 전용 속성인 value가 없으므로(TS2339), instanceof HTMLInputElement로 좁힌 뒤에야 input.value를 안전하게 읽을 수 있습니다.
주문 수량 입력 칸(#qty)에서 값을 꺼내는 브라우저 코드를 타입 검사기로 점검하면서, 왜 두 가지 오류가 동시에 나는지와 instanceof로 한 번에 좁히는 법을 살펴봅니다.
JS 다시 보기: 요소 찾기: querySelector, 폼 제출과 입력 값에서 querySelector로 요소를 찾고 입력 칸의 .value를 읽는 법을 다뤘습니다.
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);
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로 확인하면 두 문제가 한꺼번에 풀립니다.
null은HTMLInputElement의 인스턴스가 아니므로null가능성이 걸러집니다.- 일반
Element가운데서도value: string속성을 가진HTMLInputElement임이 확인됩니다.
const input = document
.querySelector("#qty");
if (
input instanceof
HTMLInputElement
) {
console.log(input.value);
}
검사 오류가 0이 되어 아무 메시지도 나오지 않습니다. 갈래 안에서 input의 지금 타입은 HTMLInputElement이고, input.value의 타입은 string입니다.
| 찾으려는 요소 | 좁힐 때 쓰는 클래스 | 주요 고유 속성 |
|---|---|---|
<input> 입력 칸 | HTMLInputElement | value: string, checked: boolean |
<button> 버튼 | HTMLButtonElement | disabled: boolean |
<form> 폼 | HTMLFormElement | reset(): 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);
}
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);
}
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 읽기에서는 지금까지 써 온 설정 파일의 각 줄을 읽습니다.
검사하고 돌리는 법은 내 컴퓨터에서 검사하고 돌리기에 있습니다.