개발 기술/사소하지만 놓치기 쉬운 개발 지식

[JS] event.target vs event.currentTarget의 차이 (with. vue slot)

by GicoMomg 2024. 9. 8.

1. 들어가며…

얼마 전, 특정 라이브러리의 버그를 수정하던 중 특이한 현상을 발견했다. 이 현상은 Vueslot을 핸들링하는 과정에서 발생했는데, 우선, 의도된 동작은 다음과 같다:

  1. 부모 요소(<tr>)에 클릭 이벤트가 등록된 상태일 때,
  2. 부모 영역 클릭 시, 부모 요소의 속성 값(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.targetevent.currentTarget의 차이점을 살펴보겠다. 🙂




2. event.target vs event.currentTarget의 차이

1) 개념

(1) event.target란?

  • event.target은 이벤트가 최초로 발생한 요소를 참조한다.
  • 예를 들어 버튼 안에 담긴 요소를 클릭한 경우, 그 버튼이 아닌 자식 요소가 바로 event.target이 된다.
  • 이벤트가 상위 요소로 전파되어도(버블링), event.target은 최초로 이벤트가 발생한 요소를 참조한다.

  • 아래 예시를 보면 쉽게 이해될 것이다.
  • 클릭 대상별 event.target 값을 확인하기 위해, 버튼 요소에 자식 요소(p)를 추가했다.
  • 그리고 클릭 이벤트는 button에 등록했다. 이 상태에서 글자 영역(p)을 클릭하면 event.targetp 요소를 가리키고, 글자 이외의 영역을 클릭하면 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-containerclick 이벤트 핸들러를 바인딩한다.
- 이벤트가 발생할 때 브라우저는 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.targetevent.currentTarget의 차이점과 활용 방법에 대해 알아보았다.
event.target은 정확히 어떤 요소가 클릭되었는지 알아내야 할 때 사용하며, 반면에 event.currentTarget은 이벤트가 바인딩된 요소에 대한 참조가 필요할 때 사용한다. 특히 Vue의 slot이나 React의 children을 통해 요소를 주입한 경우, 이벤트가 등록된 요소에 접근하기 위해 event.currentTarget을 사용하는 것이 안전하다.
만약 vue의 slot, react의 children을 이용해 재사용한 컴포넌트를 만들 때, 이벤트 핸들링 대상이 올바른지 체크하면 좋을 듯 하다.



반응형

댓글