...
이벤트 디스패치
자바스크립트를 사용하면 핸들러를 할당할 수 있을 뿐만 아니라 이벤트를 직접 만들 수도 있습니다.
새로운 커스텀 이벤트뿐만 아니라 목적에 따라 click, mousedown 같은 내장 이벤트를 직접 만들 수도 있습니다.
이렇게 만든 내장 이벤트들은 테스팅을 자동화할 때 유용합니다.
Event의 생성자
내장 이벤트 클래스는 DOM 요소 클래스같이 계층 구조를 형성합니다.
내장 이벤트 클래스 계층의 꼭대기엔 Event 클래스가 있습니다.
Event 객체는 다음과 같이 생성할 수 있습니다.
let event = new Event(type[, options]);
- type – 이벤트 타입을 나타내는 문자열로 "click"같은 내장 이벤트, "my-event" 같은 커스텀 이벤트가 올 수도 있습니다.
- options – 두 개의 선택 프로퍼티가 있는 객체가 옵니다.
- bubbles: true/false – true인 경우 이벤트가 버블링 됩니다.
- cancelable: true/false – true인 경우 브라우저 '기본 동작’이 실행되지 않습니다. 자세한 내용은 커스텀 이벤트 섹션에서 살펴보겠습니다.
dispatchEvent
이벤트 객체를 생성한 다음엔 elem.dispatchEvent(event)를 호출해 요소에 있는 이벤트를 반드시 '실행’시켜줘야 합니다
이렇게 이벤트를 실행시켜줘야 핸들러가 일반 브라우저 이벤트처럼 이벤트에 반응할 수 있습니다.
bubbles 플래그를 true로 해서 이벤트를 만든 경우 이벤트는 제대로 버블링 됩니다.
예시를 살펴봅시다.
자바스크립트를 사용해 click 이벤트를 만들고 실행 시켜 보았습니다.
버튼을 실제로 클릭하지 않았지만, 이벤트 핸들러가 동작하는 것을 확인할 수 있습니다.
<button id="elem" onclick="alert('클릭!');">자동으로 클릭 되는 버튼</button>
<script>
let event = new Event("click");
elem.dispatchEvent(event);
</script>
event.isTrusted
event.isTrusted를 사용하면 이벤트가 스크립트를 통해 생성한 이벤트인지 ‘진짜’ 사용자가 만든 이벤트인지 알 수 있습니다.
event의 isTrusted 프로퍼티가 true이면 사용자 액션을 통해 만든 이벤트라는 것을 의미합니다.
isTrusted가 false이면 해당 이벤트가 스크립트를 통해 생성되었다는 걸 알 수 있습니다.
커스텀 이벤트 버블링
"hello"라는 이름을 가진 이벤트를 만들고 버블링 시켜서 document에서 이벤트를 처리할 수 있게 해보겠습니다.
이벤트가 버블링되게 하려면 bubbles를 true로 설정해야 합니다.
<h1 id="elem">Hello from the script!</h1>
<script>
// 버블링이 일어나면서 document에서 이벤트가 처리됨
document.addEventListener("hello", function(event) { // (1)
alert("Hello from " + event.target.tagName); // Hello from H1
});
// 이벤트(hello)를 만들고 elem에서 이벤트 디스패치
let event = new Event("hello", {bubbles: true}); // (2)
elem.dispatchEvent(event);
// document에 할당된 핸들러가 동작하고 메시지가 얼럿창에 출력됩니다.
</script>
위 예시에서 주의해서 볼 점은 다음과 같습니다.
- on<event>은 내장 이벤트에만 해당하는 문법이기 때문에 document.onhello라고 하면 원하는 대로 동작하지 않습니다. 커스텀 이벤트는 반드시 addEventListener를 사용해 핸들링해야 합니다.
- bubbles:true를 명시적으로 설정하지 않으면 이벤트가 버블링 되지 않습니다.
내장 이벤트(click)와 커스텀 이벤트(hello)의 버블링 메커니즘은 동일합니다.
이에 더하여 커스텀 이벤트에도 내장 이벤트와 마찬가지로 캡쳐링, 버블링 단계가 있습니다.
UI 이벤트 주의점
명세서의 UI 이벤트 섹션엔 다양한 UI 이벤트 클래스가 명시되어 있습니다.
그중 일부를 추리면 다음과 같습니다.
- UIEvent
- FocusEvent
- MouseEvent
- WheelEvent
- KeyboardEvent
- 등등…
그런데 이 이벤트들은 new Event로 만들면 안 되고, 반드시 관련 내장 클래스를 사용해야 합니다.
마우스 클릭 이벤트라면 new MouseEvent("click")를 사용해야 하죠.
이렇게 제대로 된 생성자를 사용해야만 해당 이벤트 전용 표준 프로퍼티를 명시할 수 있습니다.
new MouseEvent("click")를 사용해 마우스 이벤트의 clientX, clientY 프로퍼티를 설정해 보겠습니다.
let event = new MouseEvent("click", {
bubbles: true,
cancelable: true,
clientX: 100,
clientY: 100
});
alert(event.clientX); // 100
// 작동 안됨 !!!!!!!!!!!!!!!!!!!!!!
let event = new Event("click", {
bubbles: true, // Event 생성자에선
cancelable: true, // bubbles와 cancelable 프로퍼티만 동작합니다.
clientX: 100,
clientY: 100
});
alert(event.clientX); // undefined, 알 수 없는 프로퍼티이기 때문에 무시됩니다.
커스텀 이벤트 만들기
지금까진 new Event로 커스텀 이벤트를 만들었습니다.
하지만 제대로 된 커스텀 이벤트를 만들려면 new CustomEvent를 사용해야 합니다.
CustomEvent는 Event와 거의 유사하지만 한 가지 다른 점이 있습니다.
CustomEvent의 두 번째 인수엔 객체가 들어갈 수 있는데, 개발자는 이 객체에 detail이라는 프로퍼티를 추가해 커스텀 이벤트 관련 정보를 명시하고, 정보를 이벤트에 전달할 수 있습니다.
<h1 id="elem">이보라님, 환영합니다!</h1>
<script>
// 추가 정보는 이벤트와 함께 핸들러에 전달됩니다.
elem.addEventListener("hello", function(event) {
alert(event.detail.name);
});
elem.dispatchEvent(new CustomEvent("hello", {
detail: { name: "보라" }
}));
</script>
detail 프로퍼티엔 어떤 데이터도 들어갈 수 있습니다.
사실 new Event로 일반 이벤트를 생성한 다음 추가 정보가 담긴 프로퍼티를 이벤트 객체에 추가해주면 되기 때문에 detail 프로퍼티 없이도 충분히 이벤트에 원하는 정보를 추가할 수 있긴 합니다.
그런데도 detail이라는 특별한 프로퍼티를 사용하는 이유는 다른 이벤트 프로퍼티와 충돌을 피하기 위해서입니다.
이 외에도 new CustomEvent를 사용하면 코드 자체만으로 '커스텀 이벤트’라고 설명해주는 효과가 있습니다.
동기적 처리 (이벤트 안 이벤트)
이벤트는 대게 큐에서 처리됩니다.
따라서 브라우저가 onclick 이벤트를 처리하고 있는데 마우스를 움직여서 새로운 이벤트를 발생시키면 이 이벤트에 상응하는 mousemove 핸들러는 onclick 이벤트 처리가 끝난 후에 호출됩니다.
그런데 이벤트 안 dispatchEvent 처럼 이벤트 안에 다른 이벤트가 있는 경우엔 위와 같은 규칙이 적용되지 않습니다.
이벤트 안에 있는 이벤트는 즉시 처리됩니다. 새로운 이벤트 핸들러가 호출되고 난 후에 현재 이벤트 핸들링이 재개됩니다.
예시를 살펴봅시다.
menu-open 이벤트는 onclick 이벤트가 처리되는 도중에 트리거됩니다.
menu-open 이벤트 처리는 onclick 핸들러가 끝날 때까지 기다리지 않고 바로 처리됩니다.
<button id="menu">메뉴(클릭해주세요)</button>
<script>
menu.onclick = function() {
alert(1);
menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
}));
alert(2);
};
// 1과 2 사이에 트리거됩니다
document.addEventListener('menu-open', () => alert('중첩 이벤트'));
</script>
[실행]
1
중첩 이벤트
2
얼럿창에 ‘1’, ‘중첩 이벤트’, '2’가 차례대로 출력되는 것을 확인할 수 있습니다.
이 예시에서 주목해야 할 것은 중첩 이벤트 menu-open이 document에 할당된 핸들러에서 처리된다는 점입니다.
중첩 이벤트의 전파와 핸들링이 외부 코드(onclick)의 처리가 다시 시작되기 전에 끝났습니다.
이런 일은 중첩 이벤트가 dispatchEvent일 때뿐만 아니라 이벤트 핸들러 안에서 다른 이벤트를 트리거 하는 메서드를 호출할 때 발생합니다.
즉, 이벤트 안 이벤트는 동기적으로 처리되는 것이죠.
비동기적 처리하기
그런데 때에 따라 중첩 이벤트가 동기적으로 처리되는걸 원치 않는 경우도 있기 마련입니다.
위 예시에서 menu-open이벤트나 다른 이벤트의 처리 여부와 상관없이 onclick 이벤트를 먼저 처리하려면 어떻게 해야 할까요?
- onclick 끝에 dispatchEvent 등의 이벤트 트리거 호출을 넣는 게 하나의 방법이 될 수 있습니다.
- 이에 더하여 중첩 이벤트를 지연시간이 0인 setTimeout으로 감싸는 것도 방법입니다.
<button id="menu">Menu (click me)</button>
<script>
menu.onclick = function() {
alert(1);
setTimeout(() => menu.dispatchEvent(new CustomEvent("menu-open", {
bubbles: true
})));
alert(2);
};
document.addEventListener('menu-open', () => alert('중첩 이벤트'));
</script>
[실행]
1
2
중첩 이벤트
이 글이 좋으셨다면 구독 & 좋아요
여러분의 구독과 좋아요는
저자에게 큰 힘이 됩니다.