본문 바로가기

2주차

자바스크립트의 DOM 조작과 이벤트 리스너 바인딩의 순서, 그리고 이벤트 위임

이번 2주차 과제를 하면서 처음 알게된 사실이 있습니다. 자바스크립트를 사용해서 동적으로 생성한 요소에는 이벤트 리스너가 적용되지 않는다는 사실입니다. 이걸 이제야 알다니, 제가 그동안 얼마나 자스에 소홀했는지 다시 한번 깨닫게 되었습니다. 열심히 해야겠네요..

 

🚨Emergency🚨

아무튼, 이번 과제에서 구현해야 할 기능 중 하나인 체크박스 리스트를 추적하면서 모두 체크되었을 때, 혹은 하나라도 체크 해제되었을 때 가장 상단의 전체 체크박스의 체크상태를 업데이트 해줘야하는 테스크가 있었죠.

제가 생각한 방식은 모든 체크박스의 개수와 체크박스들 중 체크가 된 요소들의 개수를 각각 세어 length가 같으면 상단의 전체 체크박스의 체크상태를 업데이트 하는 방식입니다.

const isAllChecked = () => {
  const checkBoxes = document.querySelectorAll(".check-item");
  const checkAll = document.querySelector("#check-all-btn");
  const checked = document.querySelectorAll(".check-item:checked").length;

  checkAll.checked = checkBoxes.length === checked;
};

위 코드와 같이 우선 제가 생각한 방식대로 함수를 구현했습니다.

이후 각 체크박스들을 forEach  매서드를 통해 순회하면서 change 이벤트가 발생했을 때 위 함수를 실행하여 기능을 구현했습니다.

 

 

하지만 이후 문제가 발생했습니다. 검색을 통한 필터링 기능, 새로운 멤버 데이터를 추가하는 기능을 실행한 후 새로운 데이터 리스트에서는 이 전체 체크리스트를 검증하는 함수가 정상적으로 동작하지 않는 문제였습니다.

이에 대해 찾아보니, createElement로 요소를 생성하지 않고 직접 html을 작성해서 innerHTML과 같은 방법으로 요소를 동적으로 생성하는 경우에는 이벤트 바인딩이 되지 않는다고 합니다.
이런 경우 동적으로 생성된 요소의 부모 요소 중 동적으로 생성되지 않은 요소에 이벤트를 바인딩 하면 된다는 사실을 알게 되었습니다.

 

아마 이벤트 리스너가 동적 요소에 자동으로 바인딩 되지 않는 이유가 자바스크립트의 DOM 조작 및 이벤트 처리 방식에 밀접한 관련이 있을 것 같습니다. 이에 대해 좀 더 알아볼까요?

 

 

🕹️ 자바스크립트의 DOM 조작과 이벤트 리스너 바인딩의 순서

  1. 초기 DOM 파싱 및 렌더링 : 브라우저는 HTML을 읽으면서 초기 DOM 트리를 구성합니다. 이때, 자바스크립트는 DOM을 파싱하여 필요한 요소를 선택하고, 그 요소에 이벤트 리스너를 바인딩합니다.

  2. 정적 요소에 이벤트 리스너 설정 : 이후 document.querySelector() 와 같은 선택자 메서드를 사용해 현재 DOM에 있는 요소만 선택하여 그 요소에 리스너를 설정하게 됩니다. 이때 DOM에 이미 존재하는 요소에만 리스너가 연결됩니다.

  3. 동적 요소의 생성 : 이후 자바스크립트 코드가 실행되면서 innerHTML, appendChild, createElement 등을 통해 새로운 요소가 DOM에 추가될 수 있습니다. 하지만 이러한 경우와 같이 새로 추가된 요소에는 이전에 설정된 이벤트 리스너가 연결되지 않은 상태로 남게 됩니다.

=> 이처럼 자바스크립트는 한 번 선택된 DOM 요소에 대해서만 이벤트를 연결하고, 동적으로 생성되는 요소까지 자동으로 추적하지 않기 때문에 새롭게 추가된 요소에는 리스너가 없는 상태로 남게 됩니다.

 

그렇다면 검색 기능을 통해 새로 필터링 된 데이터나, 새롭게 추가된 멤버 데이터 요소에 대해서는 어떻게 이벤트 리스너를 추가할 수 있을까요? 이를 해결하기 위해 이벤트 위임 이라는 개념에 대해 알아보겠습니다.

 

📥 이벤트 위임(Event Delegation)의 개념

이벤트 위임이란 사용자의 액션에 의해 이벤트 발생 시 이벤트 버블링에 의해 document 레벨까지 버블링 되어 올라가는데 이 때문에 자식 요소에서 발생하는 이벤트를 부모 요소에서도 감지할 수 있습니다. 이러한 작동 방식을 이용하여 자식 요소 하나하나에 모두 이벤트를 부여하는 것이 아닌 부모에게만 이벤트를 적용시키는것을 이벤트 위임이라고 합니다.

 

예를들어, nav바에 버튼이 6개가 있다면 6개의 모든 버튼에 이벤트 리스너를 달아주는 것보다 상위 요소인 nav바에 이벤트 리스너를 달아주고, 어떤 버튼이 클릭되었는지 이벤트 객체의 target 속성을 사용하여 구체화 시킬 수 있습니다.

 

👍🏻 이벤트 위임(Event Delegation)의 장점

  1. 지금 이벤트 위임에 대해 알아보는 가장 중요한 이유인 동적인 요소에 대한 이벤트 처리가 편리합니다.
  2. 상위 요소에서만 이벤트를 관리하기 때문에 하위 요소는 추가 및 삭제가 자유롭습니다.

 

😆 이벤트 위임(Event Delegation)을 통한 문제 해결

위와 같이 동적으로 생성되는 요소에 이벤트가 연결되지 않는 문제를 해결하기 위해서, 자바스크립트에서 이벤트가 상위 요소로 버블링(bubbling)되는 특징을 활용합니다. 상위 요소에 리스너를 한 번 설정해놓으면, 이후 하위 요소에서 발생한 이벤트가 상위로 전달될 때 조건에 맞는 하위 요소에 이벤트가 적용됩니다. 이와 같은 방식으로 부모 요소에 한 번만 이벤트 리스너를 설정해놓으면, 이후 동적으로 추가된 자식 요소에도 이벤트가 정상적으로 적용될 수 있습니다.

 

기존에 만들어놨던 isAllChecked() 함수를 실행하여 검색 기능을 통한 필터링 후, 새로운 멤버 데이터 요소 추가 후에도 원하는 대로 정상동작하게 하기 위해서는 각 이벤트 리스너에서 isAllChecked() 함수를 매번 실행시켜줘야 할 것 입니다.
하지만 이는 매우 번거롭고 비효율적인 코드겠죠. 이를 이벤트 위임을 통해 해결해봅시다.

 

document.querySelector("tbody").addEventListener("change", (e) => {
  if (e.target.classList.contains("check-item")) {
    isAllChecked();
  }
});

위 코드와 같이 모든 체크박스들의 상위 요소인 tbody태그에 change 이벤트를 연결시켜 클릭한 target이 check-item class를 포함하고 있을 때 isAllChecked()를 실행시키면 모든 체크박스를 클릭할 때마다 체크박스 검증 함수를 실행시킬 수 있을 것입니다.

 

😉 결론

앞선 저의 상황과 같이 동적으로 변경되는 요소들에 대해 이벤트 리스너를 동적으로 추가해줘야 하는 상황 뿐만 아니라 모든 하위 요소들에게 이벤트 리스너를 추가하는것 보다 이벤트 위임을 통해 상위 요소에 한번만 이벤트 리스너를 추가하는것이 메모리 상에도 더 효율적이라고 합니다. 역시 불필요한 코드를 효율적으로 만드는데 메모리가 빠지지 않네요.

제가 설명한 이벤트 위임에 대한 내용은 이벤트 버블링, 이벤트 캡쳐링에 대해 공부를 먼저 하고 읽으시면 좀 더 쉽게 이해하실 수 있을 것 같습니다. 

감사합니다.