EventHandler (feat. bootstrap 5)

2022년 05월 23일

bootstrap 5를 써본 사람은 알겠지만 내가 만든 UI Component가 비슷하다는 것을 알 것이다.
왜냐하면… 내가 그걸 참고 해서 만들었으니까!

부트스트랩 라이브러리를 사용했다는 건 아니고 거기에 쓰인 Components들을 보고 참고하여 만들었다.
부트스트랩의 UI Component들은 접근성도 신경 쓰고 인터랙션도 신경 쓴 아주 예쁜 친구들이다. 그래서 소스를 다운받아서 참고하여 만들었는데 그중에 유용한 유틸리티를 하나 소개하고자 글을 쓴다.

바로 EventHandler다.

구글링해도 정보를 얻을 수 없는 부트스트랩만의 작은 유틸리티이다.
정확히 무슨 기능을 하냐면 addEventListener, removeEventListener, CustomEvent를 대체하는 유틸리티이다.

간단하게 많이 쓰이는 addEventListener와 비교하여 나타내자면

// addEventListener
Element.addEventListener(type, listener, options);

// EventHandler
EventHandler.on(Element, type, listener);
EventHandler.one(Element, type, listener); // once: true

EventHandler는 이벤트 타겟도 함수 안에서 선언해준다. 이걸 보자면 jQuery과 비슷한 구조를 되어 있다.
실제로 아래 EventHandler의 4가지 기능 모두 사용법이 jQuery와 유사하다.
또한 내부적으로 jQuery도 지원해줘서 같이 사용할 수 있다.

// event-handler.js에서 발췌
const EventHandler = {
  on(element, event, handler, delegationFunction) {
    addHandler(element, event, handler, delegationFunction, false);
  },

  one(element, event, handler, delegationFunction) {
    addHandler(element, event, handler, delegationFunction, true);
  },

  off(element, originalTypeEvent, handler, delegationFunction) {
    // ...
  },

  trigger(element, event, args) {
    // ...
  },
};

하나하나 설명해보자면

  1. on은 기본 addEventListener다.

  2. one은 addEventListener의 options 중에 once: true가 걸린 기능이다. 거의 이거 때문에 EventHandler를 썼다. 내 UI Component에도 one을 쓴 게 있는데 스크립트를 한 번씩만 실행해야 하는 경우가 있다. 물론 EventHandler를 안 쓰고 아래처럼 해결할 수도 있다.

    /* 첫번째 방법... */
    Element.addEventListener(
      'transitionend',
      () => {
        /* ... */
        Element.removeEventListener('transitionend', transitionend);
      },
      { once: true }
    );
    
    /* 두번째 방법 */
    Element.addEventListener('transitionend', function transitionend() {
      /* ... */
      Element.removeEventListener('transitionend', transitionend);
    });

    첫 번째 방법은 options의 once: true를 사용한 것인데, 나도 이걸 쓰고 싶었으나… 아직 IE를 지원해야 하는 사이트가 많아서 불가능했다. 이거는 polyfill도 없더라…

    두 번째 방법은 addEventListener안에서 removeEventListener를 선언하는 것인데… 이게 무슨 문제가 있었는데… 기억이 안 난다. 그리고 무엇보다 마음에 안 든다.

  3. off는 removeEventListener와 같다.

  4. trigger는 이전 글 주제였던 CustomEvent이다.

    /* CustomEvent */
    const 변수명 = new CustomEvent('커스텀 이벤트 명', {
      detail: {
        임의값: 임의값1,
      },
    });
    타겟.dispatchEvent(변수명);
    
    /* EventHandler */
    EventHandler.trigger(타겟, '커스텀 이벤트 명', {
      임의값: 임의값1,
    });

    차이점은 보다시피 이벤트 세부 정보를 지정할 때 detail을 쓸 필요 없고,
    dispatchEvent로 따로 안 붙여도 된다. EventHandler.trigger()를 선언한 곳에서 바로 dispatch된다. 그래서 CustomEvent 변수를 따로 지정할 필요도 없다.

결과적으로 러닝 커브도 굉장히 낮은 편이고, 나름 생산성을 높여주는 유틸리티라고 볼 수 있다. 그냥 jQuery를 쓰면 되지 않느냐고 할 수 있지만 addEventListener 때문에 jQuery를 쓰는 건 너무 낭비이다. 그리고 나는 jQuery를 선호하지 않는다.

글을 쓰는 지금 2022년 5월 23일 기준으로는 굳이… EventHandler를 쓸 이유가 적어지고 있다. 왜냐하면, EventHandler를 썼던 가장 큰 이유가 IE에서 못 쓰는 걸 지원하기 위해서였는데 IE가 2022년 6월 15일부터 지원 종료된다는 소식에 프로젝트들도 차츰차츰 IE를 소홀히 하기 시작하였기 때문이다. (새로 구축 시에 아예 배제하는 건 아니더라… 제발 좀 완전히 버렸으면 좋겠는데!)
물론 IE 호환이 아니어도 꽤 매력적인 유틸리티라고 생각한다. 용량도 굉장히 작고(300줄, min: 4kb) 생산성도 좋아진다. 프론트엔드 프레임워크 위에서는 안 쓰겠지만, 그 외 번들링 위에서 작업하는 MPA라면 당분간은 계속 쓸 것 같다.