DOM (Document Object Model)
: HTML 요소를 자바스크립트 객체 (JavaScript Object)처럼 조작 (Manipulate)할 수 있는 Model.
// div 요소 만들기
document.createElement('div')
현재
tweetDiv
라는 div 요소는createElement
에 의해 노드가 생성됐다. 하지만, DOM 트리에 연결되어 있지 않아서 화면에서는 보이지 않는 상태다.
DOM으로 HTML 엘리먼트 정보를 조회하기 위해선 querySelector
메서드를 이용한다. 여러 엘리먼트를 한 번에 가져오기 위해서는 querySelectorAll
메서드를 사용하면 유사 배열로서 조회할 수 있다.
// 클래스 이름이 tweet인 HTML 엘리먼트 중 첫 번째를 조회
const oneTweet = document.querySelector('.tweet')
// 클래스 이름이 tweet인 모든 HTML 엘리먼트를 유사 배열로 조회
const tweets = document.querySelectorAll('.tweet')
// 가장 많이 쓰이는 Selector 태그
HTML : "div"
id : "#tweetList"
class : ".tweet"
querySelector는 이전 버전 브라우저와 호환되지 않을 수 있다. 예전 방식의 DOM 조작법은 다음과 같다.
// id가 container인 HTML 엘리먼트를 조회
const getOneTweet = document.getElementById('container')
// 위와 동일
const queryOneTweet = document.querySelector('#container')
// 둘은 완벽하게 같다
console.log(getOneTweet === queryOneTweet) // true
노드를 DOM 트리에 연결시키기 위해서는 append라는 메서드를 사용해야한다.
// 변수 tweetDiv를 container에 할당하기
const container = document.querySelector('#container')
const tweetDiv = document.createElement('div')
container.append(tweetDiv)
append 메서드에 의해 DOM 트리에 잘 연결되었으나, div 태그 안에 아무 내용도 없기 때문에 아무것도 보이지 않은 상태다.
textContent
를 사용하여, 비어있는 div 엘리먼트에 문자열을 입력할 수 있다.
console.log(oneDiv) // <div></div>
oneDiv.textContent = 'dev';
console.log(oneDiv) // <div>dev</div>
하지만, 현재 div 엘리먼트에 클래스 이름이 없기 때문에 CSS 스타일링이 적용되지 않는다. div 엘리먼트에 class를 추가하기 위해선 classList.add
를 사용한다.
oneDiv.classList.add('tweet')
console.log(oneDiv) // <div class="tweet">dev</div>
이제 append로 class가 생성된 div를 container의 자식 요소로 추가한다
const container = document.querySelector('#container')
container.append(oneDiv)
위 방식 이외에도 setAttribute
메서드를 사용하면, class와 id 그리고 다른 속성까지 할당할 수 있다.
// HTML
<button>This is Button</button>
// JS (DOM 조작)
var b = document.querySelector("button");
b.setAttribute("name", "helloButton");
// 이후 HTML
<button name="helloButton">This is Button</button>
다음은 DOM을 이용하여 엘리먼트를 삭제하는 법이다. 먼저, 삭제하려는 엘리먼트의 위치를 아는 경우 remove
메서드로 해당 엘리먼트를 삭제한다.
// id가 container인 엘리먼트 선택
const container = document.querySelector('#container')
// 아래 tweetDiv를 추가
const tweetDiv = document.createElement('div')
container.append(tweetDiv)
// ** tweetDiv를 제거
tweetDiv.remove()
여러 개의 자식 엘리먼트를 삭제하는 방법으로 여러가지가 있다. innerHTML
을 이용하면 쉽게 구현할 수 있지만 권장되지 않는 방법이다.
// id가 container인 엘리먼트의 모든 자식을 삭제
document.querySelector('#container').innerHTML = '';
innerHTML
은 XSS(Cross-Site Scripting) 공격에 취약하기 때문에 사용을 지양해야한다. XSS 공격이란 게시판이나 웹 메일 등에 스크립트 코드를 삽입하여 개발자가 의도하지 않은 악의적인 기능이 작동하게 하는 치명적인 공격이다. HTML5에서 innerHTML
안에 삽입된 <script>
태그가 실행되지 않도록 수정했지만, 여전히 다른 공격 루트가 존재한다.
대신, 다른 삭제하는 방법들이 존재한다. removeChild
를 반복문을 통해 여러 엘리먼트를 삭제할 수 있다.
const container = document.querySelector('#container');
// 첫 번째 자식 엘리먼트가 존재하면, container의 첫 번째 자식 엘리먼트를 제거
while (container.firstChild) {
container.removeChild(container.firstChild);
}
모든 자식 엘리먼트가 제거됐지만, “Tweet List”라는 제목까지 없어지는 부작용이 있다. 이를 해결하기 위해서 여러 방법이 있다.
const container = document.querySelector('#container');
while (container.children.length > 1) {
container.removeChild(container.lastChild);
}
const tweets = document.querySelectorAll('.tweet')
tweets.forEach(function(tweet){
tweet.remove();
})
// or
for (let tweet of tweets){
tweet.remove()
}
항상 React로만 웹 개발을 하다가 정말 오랜만에 DOM을 이용하여 간단한 회원가입 페이지를 구현해보았다. state라는 개념 없이, HTML / CSS를 조작하는 방식으로 개발을 하니 너무 적응이 안되고 불편했다. 각각의 HTML 엘리먼트를 조작하기 위해, Selector로 선택을 해야하고 classList.add
나 remove
와 같은 메서드를 호출하니 간단한 기능이지만 코드가 꽤나 길어졌다.
코드를 조금이라도 줄이기 위해서 유효성 검사 시, 클래스 이름을 조작하는 메서드의 코드들을 하나의 함수로 만들었다.
// 유효성 검사 실패 시, 실행하는 함수
const showFailure = (inputSelector, errorSelector) => {
// 인풋창 테두리를 빨간색으로 변경
inputSelector.classList.add("failure");
// 성공 시, 파란색이었던 인풋창 테두리를 제거
inputSelector.classList.remove("success");
// 에러 메시지를 빨간색으로 변경
errorSelector.classList.add("failure");
// 에러 메시지를 보이게 함
errorSelector.classList.remove("hide");
// 마지막 회원가입 버튼을 누를 시, boolean을 리턴하여 어떤 값이 잘못되었는지 알려줌
return false;
};