티스토리 뷰

3) Event delegation

이런 상황에서의 이벤트 등록(이벤트를 효율적으로 등록하는 방법)

  • 아래 화면은 가로로 배치된 책 리스트입니다.
    각각 리스트에 클릭을 할 때 어떤 이벤트가 발생해야 한다고 가정합니다.
    addEventListener를 사용해서 이벤트 등록을 할 수 있을겁니다.

  • 예제에는 4개의 li 태그가 있습니다.

  <ul>
    <li><img src="https://images-na.ssl-images-amazon.com/images/I/513hgSybYgL._AC_SY400_.jpg" class="product-image" ></li>
    <li><img src="https://images-na.ssl-images-amazon.com/images/I/41HoczBHr2L._AC_SY400_.jpg" class="product-image" ></li>
    <li><img src="https://images-na.ssl-images-amazon.com/images/I/51AEI3isFiL._AC_SY400_.jpg" class="product-image" ></li>
    <li><img src="https://images-na.ssl-images-amazon.com/images/I/51JVp8YV3ZL._AC_SY400_.jpg" class="product-image" ></li>
  </ul>

<section class="log"></section>

<style>

  li {list-style:none;}

  ul > li {
    display:inline-block;
    padding:10px;
    border:1px solid gray;
  }

  .product-image {
    width:100px;
    height:auto;
  }

</style>

 

  • li 각각에 addEventListener를 통해 이벤트를 등록합니다이 코드는 잘 동작합니다.

var log = document.querySelector(".log");

var lists = document.querySelectorAll("ul > li");

 

for(var i=0,len=lists.length; i < len; i++) {

  lists[i].addEventListener("click", function(evt) {

     log.innerHTML = "clicked" + evt.currentTarget.firstElementChild.src;

     //currentTargetli를 가리킴. firstChildtext인데 이는 li 바로 아래에 text노드가

존재하기 때문. firstElementChild element 중 첫번째 자식을 가리킴. 여기선 img

  });

}

 

  • 브라우저는 4개의 이벤트 리스너를 기억하고 있습니다.
  • 그런데 list가 훨씬 더 많다면 브라우저는 기억해야 할 이벤트 리스너도 그만큼 많아집니다비효율적이죠.  
  • 문제는 한가지 더 있습니다. 만약 list가 한 개 더 동적으로 추가된다면 추가된 엘리먼트에 이벤트 핸들링을 위해 역시 addEventListener를 해줘야 합니다. 꽤나 번거롭습니다.
  • 이를 해결하기 위해 target 정보가 우리를 돕습니다. currentTarget과는 다릅니다.
  • 이번에는 ul 태그에만 이벤트를 새롭게 등록합니다

var ul = document.querySelector("ul");

ul.addEventListener("click",function(evt) {

  console.log(evt.currentTarget, evt.target);       // img를 누르면 ul태그, img태그 순으로 출력됩니다.

});                                               // currentTarget은 리스너가 적용된 곳을, target은 이벤트가 발동된 곳을 가리킵니다.

 

  • 위의 예제에서 img를 클릭하면 currentTarget에는 ul 태그가, target에는 img 태그가 출력됩니다.
  • currentTarget은 리스너가 적용된 곳을, target에는 클릭한 지점의 정보를 알려줍니다.
  • li img 태그는 ul 태그에 속하기 때문에 ul에 등록한 이벤트 리스너도 실행이 됩니다.
  • 이것을 이벤트 버블링이라고 합니다클릭한 지점이 하위엘리먼트라고 하여도, 그것을 감싸고 있는 상위 엘리먼트까지 올라가면서 이벤트리스너가 있는지 찾는 과정입니다Bubbling은 하위에서 상위로, 안에서 밖의 순서로 이벤트가 발생합니다. 만약 img, li, ul에 각각 이벤트를 등록했었다면, 3개의 이벤트 리스너가 실행했을 겁니다
  • 비슷하게 Capturing이라는 것도 있습니다. 반대로 이벤트가 발생하는 것인데요. 상위엘리먼트에서 하위엘리먼트 순으로 내려갑니다.
  • 기본적으로는 Bubbling 순서로 이벤트가 발생합니다Capturing 단계에서 이벤트 발생을 시키고 싶다면 addEventListener 메서드의 3번째 인자에 값을 true로 주면 됩니다.

Event Bubbling  출처 https://www.grapecity.com/en/blogs/html-and-wijmo-events/

Bubbling

Capturing

three 클릭 시 출력 순서 3 > 2 > 1

three 클릭 시 출력 순서 1 > 2 > 3

참고: https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/

 

target 정보는 실제 클릭 된 하위 엘리먼트를 알려줍니다. 그리고 이 점을 이용해서 src를 추출할 수도 있습니다. 

이제 addEventListener 메서드를 한 번만 쓰면서 모든 list image 정보를 확인할 수 있습니다더구나 list 태그가 하나 더 추가된다고 하여도 문제없이 동작합니다.

var ul = document.querySelector("ul");

ul.addEventListener("click",function(evt) {

    var target = evt.target;      //객체를 캐시한다. 이렇게 변수에 저장하는 것이 중요.

    if(target.tagName === "IMG") {

      log.innerHTML = "clicked" + target.src;

    }

});

 

  • 그런데 작은 문제가 하나 더 있는 거 같네요.
  • 예제를 보면, 이미지 태그는 padding 값이 있어서, img태그와 li 태그 사이에 공백이 존재합니다.
  • 이 부분(공백)을 클릭하면 tagName LI라서 위에서 구현한 조건문으로 들어가지 않았기 때문입니다.

var ul = document.querySelector("ul");

ul.addEventListener("click",function(evt) {

  var target = evt.target;

    if(target.tagName === "IMG") {

      log.innerHTML = "clicked" + target.src;

    } else if(target.tagName === "LI") {         //li를 눌렀을 때

      log.innerHTML = "clicked" + target.firstElementChild.src;

    }

});

 

  • 위에서 말했듯이 li태그를 동적으로 추가한다해도 추가한 각각의 엘리먼트에 대해 이벤트 리스너를 추가할 필요가 없습니다. 이를 이벤트 델리게이션이라 합니다.
  • Event Delegation은 하위 엘리먼트에서 발생해야 할 이벤트를 상위 엘리먼트에 위임하는 것을 의미합니다. 효율적으로 이벤트를 등록하는 것이 가능합니다.
  • 전체코드는 아래 링크에서 확인하세요.
    실습코드 바로가기

event.stopPropagation()

  • 해당 이벤트가 전파되는 것을 막습니다. 따라서, 이벤트 버블링의 경우에는 클릭한 요소의 이벤트만 발생시키고 상위 요소로 이벤트를 전달하는 것을 방해합니다. 그리고 이벤트 캡쳐의 경우에는 클릭한 요소의 최상위 요소의 이벤트만 동작시키고 하위 요소들로 이벤트를 전달하지 않습니다.

// 이벤트 버블링 예제

divs.forEach(function(div) {

           div.addEventListener('click', logEvent);

});

 

function logEvent(event) {

           event.stopPropagation();

           console.log(event.currentTarget.className); // three

}

출처:https://joshua1988.github.io/web-development/javascript/event-propagation-delegation/#eventstoppropagation

생각해보기

  • 이벤트 버블링과 캡쳐링의 차이점은 무엇일까요?

참고 자료

[참고링크] Bubbling and capturing
https://javascript.info

 

'부스트코스 웹 프로그래밍 > 3. 웹 앱 개발: 예약서비스 1' 카테고리의 다른 글

5. WEB UI - FE (5)  (0) 2019.08.03
5. WEB UI - FE (4)  (0) 2019.08.03
5. WEB UI - FE (2)  (0) 2019.08.03
5. WEB UI - FE (1)  (0) 2019.08.03
4. Web Animation - FE (3)  (0) 2019.08.02
Comments