
요즘 난 React로만 개발하다보니 HTML을 직접적으로 제어할 기회가 많이 없었는데,
TypeScript를 공부하다보니 문득 getElementsBy나 querySelector로 가져온 데이터들도 타입검사가 필요할 것 같다는 생각이 들었다.
JavaScript에서 DOM 속성들 제어하는 문법들
JavaScript를 공부하며 메모 수준으로 쓴 글이지만 저 문법들이 HTML 속성을 제어하는 데 주로 쓰이고 있다😅
아무튼 이러한 문법으로 가져온 데이터의 타입을 검사하는 방법 그리고 narrowing하는 방법까지 알아보도록 하자.
document.getElementsByTagName() : 특정 tag명을 가지고 있는 DOM요소 제어
document.getElementsByClassName() : 특정 calss명을 가지고 있는 DOM요소 제어
document.getElementById() : 특정 id명을 가지고 있는 DOM요소 제어
document.guerySelector(#id명 또는 .class명) : 선택한 id명이나 class명을 가진 요소를 제어 (최상단 요소만 포함됨)
document.guerySelectorAll(#id, .class) : 선택한 모든 요소를 제어
<p id="title">타이틀</p>
<a class="link" href="/">링크</a>
<a class="link" href="/">링크</a>
<a class="link" href="/">링크</a>
<button id="button">버튼</button>
<script src="변환된 자바스크립트파일.js"></script>
let title = document.querySelector('#title') //id명이 title인 최상단 요소
let link = document.querySelectorAll('.link') //class명이 link인 최상단 요소
let button = document.getElementById('button') //id명이 button인 요소
우선 이렇게 하면 저장은 된다.
그렇다면
<p> 태그의 '타이틀' → 'keynene입니다',
<a> 태그의 모든 링크를 '/' → 'https://velog.io/@keynene' 로 변경하고,
<button> 태그는 클릭했을 때 '안녕하세요 keynene 입니다^^'라는 경고창이 뜨게 하려면 어떻게 해야할까
/* '타이틀' → 'keynene입니다' */
let title = document.querySelector('#title')
title.innerHTML = 'keynene입니다'
/* '/' → 'https://velog.io/@keynene' */
let link = document.querySelectorAll('.link')
link.forEach((l) => {
l.href = 'https://velog.io/@keynene'
})
/* 버튼 클릭 시 '안녕하세요 keynene 입니다^^' 경고창 */
let button = document.getElementById('button')
button.addEventListener('click', function(){
alert('안녕하세요 keynene 입니다^^')
})
console.log(title) //null
console.log(link) //null
console.log(button) //null
분명 JavaScript로 데이터 가져오는 방법까지는 확실한데, 콘솔을 찍어보면 이상하게 다 null이 출력되는 것을 확인할 수 있다.
아니 정확히는 셀렉터로 html을 찾으면 타입이 Element | null인 것을 알 수 있다.

이게 바로 TypeScript의 가장 큰 특징이고, union type으로 인해 narrowing이 필요한 이유이다.
narrowing이란 ?
이제 우리는 저 Element | null이라는 모호한 타입을 Element라고 정확히 지정해주는 narrowing이라는 작업을 하면 된다.
사실 타입검사를 하는 방법도 typeof연산자, instanceof연산자 등 방법이 많은데,
여기서는 instanceof연산자를 사용하겠다.
/* '타이틀' → 'keynene입니다' */
let title = document.querySelector('#title')
if (title instanceof HTMLElement){ //title이 HTMLElement에 포함되면 아래코드 실행
title.innerHTML = 'keynene입니다'
}
/* '/' → 'https://velog.io/@keynene' */
let link = document.querySelectorAll('.link')
link.forEach((l) => {
if (l instanceof HTMLAnchorElement){ //l이 HTMLAnchorElement에 포함되면 아래코드 실행
l.href = 'https://velog.io/@keynene'
}
})
/* 버튼 클릭 시 '안녕하세요 keynene 입니다^^' 경고창 */
let button = document.getElementById('button')
button?.addEventListener('click', function(){
alert('안녕하세요 keynene 입니다^^')
})
이렇게 하면 if문을 통해 narrowing되어 모든 데이터가 null이 아닌 Element타입일 때 정상적으로 실행되는 것을 확인할 수 있을 것이다.
그리고 button도 link와 마찬가지로
if (button instanceof HTMLButtonElement){
//실행할코드
}
로 타입검사 및 변경해도 되지만, 위와 같이 es6버전의 ?기능으로도 narrowing이 가능하다.
하지만 엄격한 타입의 구분을 위해서라면 제어를 원하는 데이터의 타입을 정확히 설계/파악해두고 instanceof HTMLElement와 같은 방법으로 narrowing해주는 것이 안전하다.
(ex. ?으로 narrowing 하면 button이 null이 아니기만하면 코드가 실행되어 버리는데, Element타입이 아닌 다른 타입이어도 코드가 실행된다는 소리니까!)
typeof, instanceof 등의 연산자로 타입검사를 하자.?도 있긴하지만 비교적 불안전하다.)