1. 들어가며…
얼마 전, 특정 라이브러리의 버그를 수정하던 중 특이한 현상을 발견했다. 이 현상은 Vue의 slot
을 핸들링하는 과정에서 발생했는데, 우선, 의도된 동작은 다음과 같다:
- 부모 요소(
<tr>
)에 클릭 이벤트가 등록된 상태일 때, - 부모 영역 클릭 시, 부모 요소의 속성 값(
data-selectable-row-id
)을emit
으로 전송해야한다.
<!-- SlotExample.vue -->
<template>
<tr @click="onClick" data-selectable-row-id="row-adevv$-1">
<slot></slot>
</tr>
</template>
<script setup>
const emit = defineEmits(['row-selected'])
function onClick(e) {
const rowId = e.target.getAttribute('data-selectable-row-id')
emit('row-selected', rowId)
}
</script>
<!-- Test.vue -->
<template>
<SlotExample @row-selected="onRowSelected">
안녕! 난 요소가 없는 단순 글자야!
</SlotExample>
</template>
<script setup>
function onRowSelected(id) {
console.log(id); // row-adevv$-1
}
</script>
+의도대로 동작한 경우 - 요소 클릭시 rowId를 출력함
하지만, 부모 요소에 slot
으로 자식 요소를 주입했을 때 의도와 다른 현상이 발생했다.
바로 사용자가 slot
으로 주입된 요소를 클릭할 시 부모의 속성 값(data-selectable-row-id
)이 null
로 반환되는 것이었다.
<!-- Test.vue -->
<template>
<SlotExample @row-selected="onRowSelected">
<div>나는 slot으로 주입된 요소야!</div>
</SlotExample>
</template>
<script setup>
function onRowSelected(id) {
console.log(id); // null
}
</script>
+의도와 다르게 동작한 경우 - slot 요소 클릭시 rowId가 null이 됨
왜 부모 요소의 속성값에 접근하지 못한 걸까?
이유는 바로 event.target
의 특성 때문이다. event.target
은 이벤트가 바인딩된 요소가 아닌, 사용자가 실제로 클릭한 요소를 참조한다.
그래서 slot
으로 주입된 자식 요소를 클릭하면, event.target
은 자식 요소를 가리키게 되고, 부모 요소의 속성에 접근할 수 없게 된다.
그럼 부모 요소에 접근하기 위해,
event.target.parentNode
를 쓰면 어떨까?
event.target.parentNode
를 쓰면, 부모 요소에 접근할 수 있다.
<!-- SlotExample.vue -->
<template>
<tr @click="onClick" data-selectable-row-id="row-adevv$-1">
<slot></slot>
</tr>
</template>
<script setup>
const emit = defineEmits(['row-selected'])
function onClick(e) {
const rowId = e.target.parentNode.getAttribute('data-selectable-row-id') // 수정!
emit('row-selected', rowId)
}
하지만 slot
은 다양한 형태의 요소를 주입할 수 있다. 만약 여러 레벨의 부모를 가진 요소를 slot
으로 주입한다면?
아래 예시와 같이 3개의 부모를 가진 요소를 slot
으로 주입하고, 글자 영역을 클릭하면...
<!-- Test.vue -->
<template>
<SlotExample @row-selected="onRowSelected">
<div class="parent-1">
<div class="parent-2">
<div class="parent-3">
안녕! 난 3개의 부모를 가진 slot 요소야!
</div>
</div>
</div>
</SlotExample>
</template>
event.target.parentNode
는 바로 직전 부모를 가리키므로, .parent-2
(아래 그림상 파란색 영역)를 참조하게 된다.
+글자를 클릭한 경우, 각 이벤트 객체가 가리키는 값
그렇다면 어떻게 이벤트가 바인딩된 요소에 접근할 수 있을까? 이 문제를 해결하려면 slot
을 사용하지 말아야 할까?slot
을 사용치 않으면 요소 커스텀에 한계가 있는데… 다행히도, 이 경우에는 event.target
대신 event.currentTarget
을 사용하면 된다!
이번 글에서는 event.target
과 event.currentTarget
의 차이점을 살펴보겠다. 🙂
2. event.target vs event.currentTarget의 차이
1) 개념
(1) event.target란?
event.target
은 이벤트가 최초로 발생한 요소를 참조한다.- 예를 들어 버튼 안에 담긴 요소를 클릭한 경우, 그 버튼이 아닌 자식 요소가 바로
event.target
이 된다. - 이벤트가 상위 요소로 전파되어도(버블링),
event.target
은 최초로 이벤트가 발생한 요소를 참조한다.
- 아래 예시를 보면 쉽게 이해될 것이다.
- 클릭 대상별
event.target
값을 확인하기 위해, 버튼 요소에 자식 요소(p
)를 추가했다. - 그리고 클릭 이벤트는
button
에 등록했다. 이 상태에서 글자 영역(p
)을 클릭하면event.target
은p
요소를 가리키고, 글자 이외의 영역을 클릭하면button
요소를 가리킨다.
<!-- html -->
<button onclick="onClick(event)" data-id="button">
<p class="child" data-id="text">안뇽!</p>
</button>
See the Pen event target example by KumJungMin (@kumjungmin) on CodePen.
(2) event.currentTarget이란?
- 반면,
event.currentTarget
은 이벤트가 바인딩된 요소를 참조한다. - 쉽게 말해, 이벤트 핸들러가 연결된 요소를 가리킨다는 말이다.
- 앞선 예시와 같은 HTML 구조이지만, 이벤트 발생시
event.target
대신event.currentTarget
을 사용했다. - 그러자 글자 영역을 클릭하든 글자 이외의 영역을 클릭하든,
event.currentTarget
은 이벤트 핸들러가 등록된button
요소를 가리켰다.
<!-- html -->
<button onclick="onClick(event)" data-id="button">
<p class="child" data-id="text"> 안뇽!</p>
</button>
See the Pen currentTarget example by KumJungMin (@kumjungmin) on CodePen.
2) 그럼 event.target은 쓸모없는 걸까?
event.currentTarget
이 더 유용하게 느껴질 수 있지만, 그렇다고 해서event.target
이 쓸모없는 건 아니다.event.target
은 이벤트가 발생한 정확한 요소를 참조할 수 있는 속성이다.- 이를 통해 사용자는 특정 자식 요소에서 이벤트가 발생했을 때 그 요소를 직접 다루거나, 그 요소에 따라 다른 동작을 수행할 수 있다.
(1) 두 속성의 조합으로 이벤트를 한 번에 처리할 수 있다.
event.currentTarget
의 특징을 이용하면, 이벤트 버블링을 활용해 여러 자식 요소에 중복으로 이벤트 핸들러를 등록하지 않아도 된다.- 예를 들어, 부모 요소에만 이벤트 핸들러를 바인딩하고, 그 자식 요소들에서 발생하는 이벤트를 한꺼번에 처리할 수 있다. 이때 자식 요소를 구별할 때는
event.target
을 사용한다. - 다음 예제에서는 부모 요소에 하나의 이벤트 핸들러를 바인딩하고, 클릭된 버튼에 따라 다른 동작을 수행하도록 했다.
<div id="button-container">
<button data-action="save">저장</button>
<button data-action="delete">삭제</button>
<button data-action="edit">편집</button>
</div>
<p class="result"></p>
const container = document.getElementById('button-container');
// (a) 부모 요소에 이벤트 핸들러를 바인딩
container.addEventListener('click', function (event) {
// (b) 클릭된 요소가 버튼인지 확인
if (event.target.tagName === 'BUTTON') {
// (c) 클릭된 버튼의 데이터 속성 가져오기
const action = event.target.dataset.action;
document.querySelector('.result').textContent = `사용자가 '${action}' 버튼을 클릭했습니다.`;
}
});
번호 | 코드 설명 |
---|---|
(a) | - button-container 에 click 이벤트 핸들러를 바인딩한다.- 이벤트가 발생할 때 브라우저는 container 요소에 바인딩된 이벤트 핸들러를 호출한다. - 여기서 event.currentTarget 은 암묵적으로 container 요소가 된다. |
(b) | 이벤트 핸들러가 바인딩된 부모 요소(button-container )를 참조한다. |
(c) | event.target 을 사용하여 클릭된 요소가 버튼인지 확인하고, 해당 버튼의 data-action 속성 값을 기반으로 다른 작업을 수행한다. |
- 동작은 아래에서 확인할 수 있다.
See the Pen event.target, event.currentTarget example by KumJungMin (@kumjungmin) on CodePen.
(2) 어떤 경우에 event.target, currentTarget을 써야할까?
그렇다면, 언제
event.target
을 사용하고, 언제event.currentTarget
을 사용해야 할까?
event.target
: 이벤트가 발생한 정확한 요소에 대한 정보가 필요할 때 사용한다. 예를 들어, 여러 자식 요소 중에서 어떤 요소가 클릭되었는지 구체적으로 알아야 하는 경우이다. 이때event.target
을 사용하면 사용자가 클릭한 특정 자식 요소를 알아낼 수 있다.event.currentTarget
: 이벤트 핸들러가 등록된 요소에 대한 정보가 필요할 때 사용한다. 예를 들어, 이벤트 핸들러가 등록된 요소 자체에서 특정 작업을 수행하거나 상태를 변경해야 할 때 유용하다.
3. 마무리하며…
이번 포스팅에서는 event.target
과 event.currentTarget
의 차이점과 활용 방법에 대해 알아보았다.event.target
은 정확히 어떤 요소가 클릭되었는지 알아내야 할 때 사용하며, 반면에 event.currentTarget
은 이벤트가 바인딩된 요소에 대한 참조가 필요할 때 사용한다. 특히 Vue의 slot이나 React의 children을 통해 요소를 주입한 경우, 이벤트가 등록된 요소에 접근하기 위해 event.currentTarget
을 사용하는 것이 안전하다.
만약 vue의 slot, react의 children을 이용해 재사용한 컴포넌트를 만들 때, 이벤트 핸들링 대상이 올바른지 체크하면 좋을 듯 하다.
'개발 기술 > 사소하지만 놓치기 쉬운 개발 지식' 카테고리의 다른 글
iOS 모바일의 보안과 이벤트 유실(feat. 사용자 활성화, 이벤트 루프) (20) | 2024.10.03 |
---|---|
[CSS/JS] ellipsis 말줄임에서 발생할 수 있는 2가지 인터렉션 문제 (0) | 2024.09.19 |
[CSS] clip-path로 별점(Rating) UI 구현하는 법 (with. vue 컴포넌트 예시 포함) (13) | 2024.08.28 |
[JS] RangeSlider를 구현하는 법(feat. 중첩 range input) (2) | 2024.07.14 |
[JS] 모달 창 띄울 때 레이아웃 깨짐 없이 스크롤 막는 법 (0) | 2024.06.11 |
댓글