[참고!] 이벤트 위임에 대해서 공부하기 위해서는 이벤트 버블링과 이벤트 캡쳐링에 대한 이해가 필요하다. 만약 관련된 지식이 없다면 이전 포스트를 먼저 읽고 오길 바란다.
이벤트 위임 (Event Delegation)
이벤트 캡쳐링과 이벤트 버블링을 활용하면 이벤트 위임이라는 강력한 이벤트 핸들링 패턴을 활용할 수 있다. 이벤트 위임은 비슷한 방식으로 작동하는 여러 가지 엘레먼트들이 존재할 때 그들의 공통 조상에 이벤트 핸들러를 부여함으로써 편리하게 이벤트를 핸들링하는 방법이다.
각각의 element에 직접적으로 이벤트 핸들러를 부여하는 것이 더 직관적이고 단순한 방법 같은데 왜 굳이 조상 엘레먼트까지 올라가야할지에 대한 의문이 생길 수 있다. 그래서 예시를 가져와봤다.
위 코드에서 box들은 정상적으로 작동한다. 그러나 새로운 box를 추가하고 싶다면 매번 이벤트 핸들러를 추가해줘야 할 것이다. 이는 여간 번거로운 작업이 아닐 수 없다. 아마 vanilla JS를 처음 배워서 웹 페이지를 제작해본 경험이 있는 사람이라면 내가 무슨 말을 하는지 이해할 수 있을 것이다. 만약 container가 그 자식들의 이벤트 핸들러를 모두 책임져준다면 훨씬 편리하게 이벤트를 처리할 수 있다. onclick이나 addEventListener 등으로 가득찬 코드보다는 그 편이 훨씬 보기도 좋았다.
이전 포스트에서 언급되었다시피 event.target을 활용하면 실제 이벤트의 타겟 element에 접근할 수 있다. 순간적으로 문제가 엄청 쉽게 해결된 것처럼 보인다. 아래와 같이 container에서 이벤트 핸들러를 추가해준 뒤 그 안에서 event.target의 className이 box면 alert( )를 실행하게 하면 되지 않을까?
혹시나 container 안에 box가 아닌 요소들이 들어갈 수도 있으니 class명을 검사하는 조건문까지 넣어줬다. 기가 막히는 해결법이 나온 것 같지만 실상은 그렇지 않다. 일반적으로 element의 구조는 저렇게 단순하지 않다. box의 구조를 아주 조금 바꿔서 안에 span을 넣어주자. 그러고 나서 코드를 실행한 뒤 box를 클릭했다. 그런데 분명 box 안을 클릭했음에도 불구하고 alert( ) 함수가 트리거되지 않는다. 아주 운이 나쁘게도 box div 자체를 클릭한 것이 아니라 span 텍스트를 클릭해버리고 만 것이다. 그렇다면 event.target은 span이 되어버린다. box는 span의 클래스명에 존재하지 않는다. (만약 그렇다면 그것은 정말 잘못된 코드일 것이다. 부모 엘레먼트와 자식 엘레먼트가 같은 클래스명을 공유한다니, 얼마나 끔찍한가!)
여기서 우리가 방금 전에 공부한 이벤트 흐름이 빛을 발한다. event.target이 목표 대상인 element이거나 혹은 그 안에 있는 element인지 검사해준 후 그 결과가 참이라면 이벤트 핸들러를 실행해줄 것이다. 얼른 이 기막힌 방법을 코드에 적용해보자.
(1) container 안에서 클릭이 이루어지면 클릭된 element에서 box라는 클래스명을 가진 가장 가까운 element를 찾는다.
(2) 만약 box가 없거나 해당 box가 container 안에 존재하는 것이 아니라면 이벤트 핸들러를 실행시키지 않는다.
※ Element.closest(selector) : 해당 Element부터 출발하여 윗쪽 방향으로 셀렉터에 부합하는 가장 가까운 Element를 반환한다. 만약 기준에 부합한다면 자기 자신이 리턴될 수도 있으며 만약 부합하는 element가 없다면 null이 반환된다.
응용 - data 속성 활용하여 custom 핸들러 지정해주기
data 속성을 활용하여 추가 element에 추가 정보를 입력해주면 이벤트 위임의 장점을 가져가면서 element마다 특수한 이벤트 핸들러를 지정해줄 수 있다.
위 예시는 아주 data 속성을 활용하여 이벤트 위임을 구현한 아주 간단한 예시이다. container 내부의 버튼이 클릭되면 data-alert-text의 속성값이 alert 창으로 제공되며 만약 해당 속성이 존재하지 않는다면 alert( )는 실행되지 않을 것이다.
이번에는 조금 더 복잡한 예시로 자식마다 다른 이벤트 핸들러가 실행되고 그 이벤트 핸들러들을 조상 element에서 관리하는 코드를 작성해보겠다.
이제 버튼을 클릭하면 data-action 속성값에 해당하는 메소드를 실행할 수 있다. 참고로 this.onClick.bind(this) 문장은 이벤트 핸들러 내부의 this를 Container 인스턴스로 지정해주기 위해서이며 (undo, reload, save 등의 메소드를 실질적으로 사용할 수 있도록) this[action]에서 볼 수 있듯이 클래스 내부에서 this[메소드명]을 작성하면 dynamic하게 클래스 메소드를 실행할 수 있다. (물론 위와 같은 예시는 핸들링 메소드의 종류가 얼마 되지 않을 때 적합한 것 같다. 만약 이벤트 핸들러의 종류가 천차만별이라면 그냥 button 클래스를 별도로 만들어주는 것이 바람직해 보인다.)
해당 포스트를 작성하기 위해서 참고하였던 게시글 중 하나(링크)에서 이벤트 위임을 연습하기 위한 몇 가지 과제들을 제시해주었다. 관련 코드들은 깃허브 링크를 통해 확인할 수 있다.
'프론트엔드 기본개념 복습 > Javascript' 카테고리의 다른 글
[Javascript] 가비지 컬렉션 (Garbage Collection) (0) | 2022.03.02 |
---|---|
[Javascript] 자바스크립트의 배열 (Array) (0) | 2022.03.01 |
[Javascript] 이벤트 버블링, 이벤트 캡쳐링 (Event Bubbling, Event Capturing) (0) | 2022.02.24 |
[Javascript] 클로저 (Closure) + Lexical Scope (0) | 2022.02.24 |
[Javascript] this (0) | 2022.02.24 |