커스텀 이벤트와 이벤트 디스패치

커스텀 이벤트란?

커스텀 이벤트는 내장 이벤트(예: click, keydown)와 달리 개발자가 자유롭게 이름을 정하고 추가 데이터를 함께 전달할 수 있는 이벤트입니다.

이벤트 생성과 디스패치

일반 이벤트 생성

  • new Event("이벤트명", options)를 사용하면 간단한 이벤트를 만들 수 있습니다. 단, 이 경우 bubbles와 cancelable 옵션만 적용됩니다.

커스텀 이벤트 생성

  • new CustomEvent("이벤트명", { detail, bubbles, cancelable })를 사용하면 추가 데이터를 담은 이벤트를 생성할 수 있습니다.

이벤트 객체를 생성한 후에는 대상 요소의 dispatchEvent() 메서드를 호출해 이벤트를 실행합니다. bubbles가 true일 경우 이벤트가 버블링되어 상위 요소에서도 처리할 수 있습니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>커스텀 이벤트: 인증 처리</title>
</head>
<body>
  <div id="auth-container">사용자 인증</div>
  <script>
    const authContainer = document.getElementById('auth-container');

    // 인증 완료 이벤트 감지
    authContainer.addEventListener('authSuccess', (event) => {
      console.log('인증 성공:', event.detail.user);
    });

    // 인증 함수
    function authenticateUser(username) {
      const event = new CustomEvent('authSuccess', {
        detail: { user: username, timestamp: Date.now() },
        bubbles: true,
        cancelable: true
      });
      authContainer.dispatchEvent(event);
    }

    // 인증 시뮬레이션
    authenticateUser('홍길동');
  </script>
</body>
</html>

버블링과 cancelable 옵션

커스텀 이벤트는 bubbles와 cancelable 옵션으로 동작을 제어합니다

bubbles

  • true로 설정하면 이벤트가 DOM 트리 상에서 버블링되어, 상위 요소에서도 해당 이벤트를 감지할 수 있습니다.

cancelable

  • true로 설정하면 이벤트 핸들러 내에서 event.preventDefault()를 호출해 기본 동작을 취소할 수 있습니다.
  • 이벤트 디스패치 메서드(dispatchEvent)는 기본 동작이 취소되었을 경우 false를 반환합니다.
<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>커스텀 이벤트: 작업 승인</title>
</head>
<body>
  <section id="task-manager">
    <div id="task">작업 영역</div>
  </section>
  <script>
    const taskManager = document.getElementById('task-manager');
    const task = document.getElementById('task');

    // 상위에서 승인 여부 판단
    taskManager.addEventListener('taskRequest', (event) => {
      if (!confirm('작업을 승인하시겠습니까?')) {
        event.preventDefault();
        console.log('승인 거부');
      }
    });

    // 작업 요청 함수
    function requestTask(taskName) {
      const event = new CustomEvent('taskRequest', {
        detail: { task: taskName },
        bubbles: true,
        cancelable: true
      });
      const approved = task.dispatchEvent(event);
      if (approved) {
        console.log(`작업 "${taskName}" 실행`);
      } else {
        console.log(`작업 "${taskName}" 취소됨`);
      }
    }

    // 테스트
    requestTask('데이터베이스 백업');
  </script>
</body>
</html>
  • taskRequest 이벤트는 상위 taskManager로 버블링되어 승인 확인.
  • preventDefault() 호출 시 dispatchEvent()가 false 반환, 작업 취소.

내장 이벤트 디스패치와 비교

내장 이벤트의 경우, 해당 이벤트에 맞는 생성자를 사용해야 합니다. 예를 들어, 마우스 이벤트를 만들 때는 new MouseEvent("click", options)를 사용해야 하며, 이를 통해 clientX, clientY와 같은 속성을 설정할 수 있습니다. 반면, new Event()로 생성한 이벤트는 제한된 옵션만 지원하므로, UI 이벤트 전용 속성들은 설정되지 않습니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>내장 이벤트: 키보드 입력</title>
</head>
<body>
  <input id="textInput" placeholder="여기에 입력">
  <script>
    const input = document.getElementById('textInput');

    // 키 입력 이벤트 수신
    input.addEventListener('keydown', (event) => {
      console.log(`키 입력: ${event.key} (코드: ${event.code})`);
    });

    // 키보드 이벤트 디스패치
    const keyEvent = new KeyboardEvent('keydown', {
      key: 'Enter',
      code: 'Enter',
      bubbles: true,
      cancelable: true
    });
    input.dispatchEvent(keyEvent);
  </script>
</body>
</html>
  • new Event('keydown')로는 key, code 같은 속성을 설정할 수 없음.

이벤트 실행 순서와 비동기 처리

일반적으로 이벤트 핸들러는 이벤트 큐를 통해 순차적으로 처리됩니다. 그러나 이벤트 핸들러 내부에서 다른 이벤트를 직접 디스패치하는 경우, 중첩 이벤트는 즉시 처리됩니다.
이런 동기적 처리 방식이 원치 않는 경우, setTimeout을 사용해 디스패치를 지연시키면 현재 이벤트 핸들링이 종료된 후에 처리할 수 있습니다.

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <title>커스텀 이벤트: 비동기 처리</title>
</head>
<body>
  <button id="trigger">트리거</button>
  <script>
    const trigger = document.getElementById('trigger');

    trigger.addEventListener('click', () => {
      console.log('1. 클릭 이벤트 시작');
      setTimeout(() => {
        trigger.dispatchEvent(new CustomEvent('followUp', { bubbles: true }));
      }, 0);
      console.log('2. 클릭 이벤트 종료');
    });

    document.addEventListener('followUp', () => {
      console.log('3. 후속 이벤트 처리');
    });

    // 클릭 시뮬레이션
    trigger.dispatchEvent(new Event('click', { bubbles: true }));
  </script>
</body>
</html>

출력 순서

  • 1. 클릭 이벤트 시작
  • 2. 클릭 이벤트 종료
  • 3. 후속 이벤트 처리