요즘 난 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
등의 연산자로 타입검사를 하자.?
도 있긴하지만 비교적 불안전하다.)