웹 페이지에서 어떤 이벤트가 발생하게 되면, 해당 이벤트와 관련된 정보를 담은 이벤트 객체가 생성된다. 우리가 자바스크립트를 통해 이벤트를 다루고자 할 때 일반적으로 어떤 한 요소에 이벤트 핸들러를 등록하게 되는데, 이때 이벤트 핸들러의 첫번째 파라미터에 바로 이 이벤트 객체가 전달된다.
const myInput = document.querySelector("#myInput");
function handleInputClick(e) {
// target은 이벤트가 발생한 요소
e.target.classList.add("active");
}
myInput.addEventListener("click", handleInputClick);
일반적으로 e
또는 event
라는 이름으로 등록하게 되는데, 이 이벤트 객체의 target
프로퍼티를 이용해서 실제로 이벤트가 발생한 요소가 어떤 것인지 파악하고 해당 요소(를 포함한 그 주변 요소들까지)를 다룰 수 있게 된다.
만약 예시처럼 myInput
이라는 단 하나의 요소만 이벤트 핸들러의 영향을 받는다면 우리가 굳이 target
프로퍼티를 이용할 필요는 없다. 하지만 아래와 같은 상황에서는 어떨까?
<ul class="book__list">
<li class="book__item">Book A</li>
<li class="book__item">Book B</li>
<li class="book__item">Book C</li>
<li class="book__item">Book D</li>
<li class="book__item">Book E</li>
<li class="book__item">Book F</li>
</ul>
book__list
라는 리스트 묶음 안에 다양한 책이 각각의 <li>
태그로 들어가있다. 사용자가 해당 책(<li>
태그)에 mouseover
할 때마다 대상 <li>
태그의 스타일을 바꾸고 싶다.
쉽게 생각하면 각각의 <li>
태그에 이벤트 핸들러를 따로따로 등록하면 된다. 하지만 그 경우 반복되는 코드가 많아 경제적이지 못하고, 이후에 새롭게 추가되는 <li>
태그가 있다면 해당 태그에 또 이벤트 핸들러를 등록해주어야 한다.
이럴때 사용하게 되는 것이 이벤트 위임인데, 이를 위해서는 DOM 이벤트 흐름에 대한 이해가 필요하다.
표준 DOM 이벤트에서 정의한 이벤트 흐름은 총 3가지 단계로 이루어진다.
이벤트 캡처링 → 타깃 → 이벤트 버블링
이벤트 캡처링 단계는 이벤트가 최상위 단계부터 타깃 요소까지 차례로 전파되는 단계다. 앞서 이벤트에 대해 설명할 때, 웹 페이지에서 이벤트가 발생하면 이벤트 객체가 생성된다고 언급했었다. 이렇게 생성된 이벤트 객체는 우선 최상위 요소인 window 전역 객체에서부터 시작해서 이벤트 타깃 방향으로 전파된다.
실제로 캡처링 단계를 이용하는 일은 드물다고 한다. 만약 캡처링 단계를 이용하고 싶은 경우, 이벤트 핸들러를 등록할 때 addEventListener
의 세 번째 아규먼트를 capture: true
또는 true
로 지정하면 된다.
이 "이용"이 뭔지는 실제로 자주 활용하게 되는 이벤트 버블링 단계와 이벤트 위임을 설명할 때 함께 다루도록 하겠다.
생성된 이벤트 객체가 실제 타깃 요소에 전달되는 단계다. (끝)
우리가 많이 활용하게 될 이벤트 버블링 단계다. 이벤트 캡처링은 최상위 요소부터 타깃 요소 직전까지, 타깃 단계는 타깃 요소에 전파되는 단계였다. 그럼 다음은 뭘까?
이벤트 버블링 단계는 다시 타깃 요소부터 최상위 요소까지 이벤트가 전파되는 단계다. 흐르는 강물을 거꾸로 거슬러 오르는 이벤트들의 도무지 알 수 없는 그들만의 신비한... 아무튼, 이 이벤트 버블링 단계에서의 특이한 점이라고 한다면 이벤트가 최상위 요소까지 전파되는 과정에서 동일한 타입의 이벤트에 한해 상위 요소들의 이벤트 핸들러도 함께 동작하게 된다는 것이다.
<div class="boxA">
<div class="boxB">
<div class="boxC"></div>
</div>
</div>
const boxA = document.querySelector(".boxA");
const boxB = document.querySelector(".boxB");
const boxC = document.querySelector(".boxC");
function handleBoxClick(e) {
// currentTarget은 버블링이 발생할 때 실제 이벤트 핸들러가 등록된 요소에 접근한다.
// 이벤트 객체의 target은 버블링이 발생해도 변하지 않는다. (boxC)
e.currentTarget.classList.add("clicked");
};
boxA.addEventListener("click", handleBoxClick);
boxB.addEventListener("click", handleBoxClick);
boxC.addEventListener("click", handleBoxClick);
위와 같은 구조에서 boxC
를 클릭한다고 생각해보자. 이벤트 타깃은 boxC
가 될 것이다. 우선 타깃에 해당하는 boxC
에 등록된 이벤트 핸들러가 동작해 boxC
에 clicked
클래스가 추가된다. 그리고 최상위 요소까지 이벤트가 전파되는 버블링 과정에서 boxB
에 등록된 이벤트 핸들러가 동작하고, 그 다음은 boxA
에 등록된 이벤트 핸들러가 동작할 것이다. 결과적으로 boxA
, boxB
, boxC
모두 clicked
클래스가 추가된다.
만약 이벤트 버블링을 멈추고 싶다면, 이벤트 객체에 stopPropagaiton()
메소드를 사용하면 된다. 즉 이벤트 동작이 있는 함수 내부에 e.stopPropagation()
과 같은 코드를 적어주면 된다. (캡처링이든 버블링이든 stopPropagation()
메소드를 사용하면 이벤트 핸들러가 한 번만 동작하고 더이상 전파되지 않는다.)
그러나 이벤트 버블링을 막는 일은 피하는 것이 좋다. 이벤트 버블링을 아래와 같이 활용할 수 있고, 이벤트 버블링에 대해 잘 이해하고 설계하게 된다면 굳이 버블링을 막을 이유 또한 없기 때문이다.
이벤트 위임이란 이벤트 버블링을 이용해 요소 각각에 이벤트 핸들러를 등록하지 않고 부모(또는 상위) 요소에만 이벤트 핸들러를 등록해 활용하는 것이다.
<ul class="book__list">
<li class="book__item">Book A</li>
<li class="book__item">Book B</li>
<li class="book__item">Book C</li>
<li class="book__item">Book D</li>
<li class="book__item">Book E</li>
<li class="book__item">Book F</li>
</ul>
const bookList = document.querySelector(".book__list");
function handleBookMouseover(e) {
e.target.classList.add("focus");
};
function handleBookMouseout(e) {
e.target.classList.remove("focus");
};
bookList.addEventListener("mouseover", handleBookMouseover);
bookList.addEventListener("mouseout", handleBookMouseout);
위에서 예시로 들었던 book__list
를 다시 한 번 살펴보자. 이제 우린 이벤트 위임을 통해 오직 한 요소, 바로 book__list
에만 이벤트 핸들러를 등록하면 된다. 그럼 target
프로퍼티를 통해 이벤트가 발생한 요소를 특정하여 이벤트 핸들러가 등록된 요소가 아닌, 해당 요소에만 기능을 부여하는 것이 가능해진다. 만약 하위에 다른 자식 요소가 더 추가된다고 하더라도 별도로 이벤트 핸들러를 추가로 등록할 필요가 없어진다.
단, 이벤트 대상이 될 수 있는 요소를 특정하지 않으면 작동하기를 바라지 않았던 요소에도 이벤트핸들러가 동작할 수 있다. 위의 예시에서는 <li>
태그 뿐만 아니라 이벤트 핸들러를 등록한 book__list
도 focus
클래스가 추가되거나 삭제될 것이다.
따라서 if (e.target.tagName === "LI")
와 같은 조건문을 이용해 이벤트 핸들러가 동작할 요소를 특정해주는 것이 좋다.
HTTP 메소드란 클라이언트(일반적으로 유저의 웹 브라우저)-서버(서비스를 제공하는 컴퓨터) 구조에서 요청과 응답, 즉 Request와 Response가 이루어지는 방식을 말한다. 리퀘스트에 어떤 메소드를 사용하냐에 따라 서버의 응답이 달라지기 때문에, 서버가 주어진 리소스를 어떻게 처리(수행)하기를 원하는지 우리가 그 행동을 지정하는 방법이라고 생각하면 될 것 같다. 좀 더 쉽게 생각하자면, 리퀘스트에 여러 종류가 있다고 생각하면 된다.
하나의 리퀘스트는 Head와 Body로 이루어져 있으며, 이중 Head에 메소드 정보가 존재한다. Body는 실제 데이터를 담는 부분인데, 메소드에 따라 Body가 필요없는 경우도 있다. (서버에 우리가 데이터를 전달할 필요가 없을때)
서버에 리퀘스트를 보내는 함수는 fetch()
함수로 소괄호 사이에 리퀘스트를 보낼 URL 주소를 넣으면 된다. 이후 서버로부터 리스폰스가 오면 그 리스폰스를 받고, 뒤에 연결될 콜백 함수를 통해 전달받은 리스폰스(프로미스 객체)를 다루게 된다.
HTTP 메소드에는 다양한 종류가 있는데, 그중 대표적인 것은 아래 다섯가지다.
GET
메소드는 리소스를 조회하는 메소드이다. 즉 서버에서 해당 리소스에 대한 정보를 받아온다. 단순히 서버에서 리소스에 대한 정보를 요청하는 것이기 때문에, Body가 필요없다.
서버에서 받아온 리소스를 실제로 자바스크립트 환경에서 객체 데이터로 활용하기 위해서는 JSON.parse(데이터)
와 같이 JSON
객체의 parse
메소드를 사용해야 한다. 그렇지 않으면 단순 String
타입의 데이터이기 때문에 우리가 실제 데이터로는 활용할 수 없다.
이처럼 String
타입의 JSON
파일을 자바스크립트의 객체로 변환하는 것을 역직렬화(Deserialization)라고 한다.
fetch
함수에 별도의 옵션을 주지 않으면 기본적으로 GET
리퀘스트를 보낸다. 다른 메소드를 사용하기 위해서는 별도의 옵션 객체가 필요하다. fetch()
함수의 두 번째 아규먼트로 method
와 body
프로퍼티가 있는 옵션 객체를 넣어주면 된다.
POST
메소드는 데이터를 처리/등록하는 메소드이다. 우리가 전달해야 하는 데이터가 있으므로 body
가 필요하다. 전달할 데이터를 옵션 객체의 body
프로퍼티 값으로 입력하면 되는데, 이때 JSON
객체의 stringify(데이터)
메소드를 통해 우리가 전달할 데이터(자바스크립트 객체)를 String
타입의 JSON
데이터로 변환하여 전달해야 한다. stringify()
를 통해 자바스크립트 객체를 String
타입으로 변환하는 것을 직렬화(Serialization)라고 한다.
stringify()
메소드와 parse()
메소드는 서로 정반대의 기능을 하며, 서버와 데이터를 주고 받을때에는 이 두 개의 메소드를 활용해야하니 반드시 기억해두자.
PUT
메소드는 리소스를 대체(덮어쓰기)하거나 새로 생성한다. 특정 데이터를 대체하기를 원하면 클라이언트가 리소스의 구체적인 경로를 지정해 요청해야한다. (URL의 path)
이전까지는 .../members
라는 URL에 정보를 요청하거나 등록했다면, PUT
메소드는 .../members/10
과 같이 member 중에서 누구의 값을 덮어쓰기할 것인지 요청하는 URL 자체에 지정해야 한다. 이는 GET
메소드에서도 통용되는 말인데, 전체가 아닌 특정 데이터를 원할 경우 URL path를 지정하여 리퀘스트를 보내면 된다.
PUT
메소드는 부분 수정이 불가능하며 해당 데이터 전체를 덮어쓰는 것임을 주의해야 한다. POST
메소드와 마찬가지로 우리가 전달해야 하는 데이터가 있으므로 body
가 필요하다.
PATCH
메소드는 PUT
과 같이 리소스를 수정하지만, PUT
과는 다르게 리소스의 일부만 변경하는 메소드다. PUT
의 경우 기존에 { A: 10, B: 20 }
이라는 값이 있었을 때 { B: 30 }
이라는 데이터를 전달하면 { B: 30 }
이 해당 데이터의 최종 값이 된다. PATCH
메소드는 동일한 경우 { A: 10, B: 30 }
이 최종값이 된다.
역시 우리가 전달해야 하는 데이터가 있으므로 body
가 필요하다.
DELETE
메소드는 리소스를 삭제한다. 특정 데이터를 지정해야 하므로 URL path를 지정해야 하며, 별도로 전달할 데이터가 없으니 body
프로퍼티는 필요없다.
HTTP 메소드가 가진 속성 중 하나이다. 멱등성(Idempotent)은 여러 번 동일한 요청을 보내는 것과 한 번 보내는 것이 똑같은 효과를 지니고, 서버 상태에도 영향을 미치지 않는 (한 번 보냈을 때의 의도한 영향 외의 부작용을 낳지 않는) 경우를 말한다.
GET
, PUT
, DELETE
메소드 같은 경우가 멱등적이고, POST
, PATCH
메소드는 멱등적이지 않다. (PATCH
메소드를 멱등적이라고 착각할 수 있으나, 값을 추가하는 리퀘스트에도 사용되기 때문에 항상 멱등성을 유지하지는 않는다.)
HTTP 메소드에 멱등성이 중요한 이유로는 실패한 요청의 재시도 때문이다. 이미 리소스가 처리되었는데 중복하여 리퀘스트를 보낼 수도 있고, 특히 결제 같은 경우에 이런 문제가 발생하면 큰 문제가 되기 때문에 멱등한 요청인지를 확인할 필요가 있다.