
웹/프론트엔드 개발을 하다 보면, 특히 UI가 복잡해지거나 요소가 동적으로 생성되면서 이벤트 핸들러 수가 폭발적으로 늘어나는 문제에 직면하곤 합니다.
링크, 폴더, 리스트, 버튼 등 반복적 요소에 하나하나 이벤트를 붙이는 방식은 단기적으로는 작지만, 장기적으로 보면 유지보수·성능·버그 관점에서 골치 아픈 원인이 됩니다.
이 글에서는 이벤트 위임(Event Delegation)의 개념, 왜 쓰는지, 언제 쓰면 좋고 언제 피해야 하는지, 실제 코드 예제와 함께 링크/폴더 구조 같은 상황에서 적용할 수 있는 구체적인 해결 방안을 다룹니다.
이벤트 위임(Event Delegation)은 다음 개념들의 조합을 활용합니다:
이벤트 버블링 (Event Bubbling)
자식 요소에서 발생한 이벤트가 DOM 트리 상위로 전파(flows up)되는 특성입니다.
상위 요소에 이벤트 리스너를 한 번만 등록
여러 하위 요소(each child)에 개별적으로 onClick/onDoubleClick 등을 붙이는 대신, 상위(wrapper 또는 컨테이너) 요소에 이벤트 리스너를 걸어두고, 이벤트가 상위까지 올라왔을 때 어느 자식에서 일어났는지 평가하여 처리합니다.
대상(target) 판별 로직
event.target, event.target.closest(selector), matches(), dataset 속성 등을 활용해 클릭된 요소가 어떤 타입(예: 링크 vs 폴더 vs 특정 버튼)인지 구분합니다.
링크(link)와 폴더(folder)에 각각 onClick, onDoubleClick 등을 직접 붙이는 기존 방식이 가진 구체적인 단점들은 다음과 같습니다:
| 항목 | 문제 상세 |
|---|---|
| 핸들러 중복 및 메모리 낭비 | 동일한 함수/로직이 여러 요소에 중복되어 붙기 때문에, 메모리 사용량 증가. 특히 리스트 항목이 많고 동적으로 추가/제거 되는 경우 부담 커짐. |
| 동적 요소 관리의 번거로움 | 스크립트로 요소가 추가되면 또 이벤트를 붙여야 함. 실수로 누락되면 해당 새 요소는 작동 안 함. |
| 가독성 저하 / 코드 산만화 | JSX나 HTML/템플릿에 이벤트 속성이 잔뜩 생김. 이벤트 로직이 요소마다 흩어져 있어 전체 흐름 파악이 어려움. |
| 성능 저하 가능성 | 브라우저가 많은 이벤트 리스너를 다루느라 렌더링이나 이벤트 처리 시 비용이 높아짐. 특히 모바일 기기나 저사양 환경에서 체감 가능. |
링크+폴더 예제에서는 “링크 클릭” / “폴더 더블클릭” 등의 동작이 반복 요소마다 있고, 어떤 경우엔 폴더가 동적으로 추가되기도 할 것임. 이런 구조에서는 위임이 유리합니다.
아래는 링크/폴더 구조에 이벤트 위임을 적용하는 실전 예제입니다.
<ul id="item-list">
{items.map(item => (
<li key={item.id}
data-type={item.type} // "link" 또는 "folder"
data-id={item.id}>
{item.type === "link"
? <a href={item.url} className="item-link"> {item.name} </a>
: <span className="item-folder"> {item.name} </span>}
</li>
))}
</ul>
const itemList = document.getElementById('item-list');
// 단일 클릭 처리
itemList.addEventListener('click', function(event) {
const li = event.target.closest('li');
if (!li) return;
const type = li.dataset.type;
const id = li.dataset.id;
if (type === 'link') {
// 링크 클릭 동작
console.log(`링크 클릭됨: ${id}`);
// 여기서는 기본 동작(navigation) 허용하거나
// event.preventDefault() 등 제어 가능
}
// 폴더 클릭 시 (폴더 단축 클릭) 다른 동작이 필요하다면 여기에
});
// 더블클릭 처리 (폴더 전용)
itemList.addEventListener('dblclick', function(event) {
const li = event.target.closest('li');
if (!li) return;
const type = li.dataset.type;
const id = li.dataset.id;
if (type === 'folder') {
console.log(`폴더 더블클릭됨: ${id}`);
// 폴더 더블클릭 처리
}
});
단일 클릭과 더블클릭이 같은 요소에서 모두 가능할 때, 일반적으로 클릭 이벤트가 먼저 실행되고 더블클릭이 이어지는 구조여서 클릭 동작과 더블클릭 동작이 서로 간섭할 수 있음.
해결책:
타이머 기반 분기
더블클릭이 아닌 단일 클릭으로 보고 일정 시간 이후(예: 300ms) 클릭 로직 실행.
이벤트 취소
더블클릭 이벤트가 발생했을 때 단일 클릭 이벤트의 실행을 막거나 무시하도록 로직 구성.
예시 (간단한 타이머 방식):
let clickTimer = null;
const clickDelay = 300;
itemList.addEventListener('click', function(event) {
const li = event.target.closest('li');
if (!li) return;
const type = li.dataset.type;
const id = li.dataset.id;
// 링크 단일 클릭만 처리
if (type === 'link') {
// 링크는 단일 클릭만 필요하다면 바로 처리
handleLinkClick(id);
} else if (type === 'folder') {
// 폴더는 단일 클릭 vs 더블클릭 분기
clearTimeout(clickTimer);
clickTimer = setTimeout(() => {
handleFolderSingleClick(id);
}, clickDelay);
}
});
itemList.addEventListener('dblclick', function(event) {
const li = event.target.closest('li');
if (!li) return;
const type = li.dataset.type;
const id = li.dataset.id;
if (type === 'folder') {
clearTimeout(clickTimer);
handleFolderDoubleClick(id);
}
});
핸들러 수 감소 → 메모리 & 이벤트 리스너 관리 비용 절감
반복되는 하위 요소가 많을수록 이득이 커짐. 동적 요소 생성에도 추가 처리 필요 없음.
코드 일관성 / 유지보수성 향상
이벤트 로직이 한 곳에 집중됨. 수정/추가/버그 수정 시 변경 지점을 최소화 가능.
성능 개선
많은 이벤트 리스너가 걸려 있는 경우, 브라우저 이벤트 처리 및 가비지 컬렉션 부담 감소.
확장성
요소 타입이 추가되더라도, 상위 로직 쪽에서 타입만 판별하여 처리하면 됨.
| 항목 | 설명 |
|---|---|
| 이벤트 버블링이 안 되는 경우 | stopPropagation()이 사용되었거나, Shadow DOM 내 특정 이벤트, 혹은 캡슐화된 컴포넌트 등에서는 이벤트가 상위로 전달되지 않을 수 있음. |
| 이벤트 타입에 따른 충돌 | 클릭/더블클릭/드래그/마우스 무브 등의 이벤트가 복합될 경우, 단일 이벤트와 더블클릭 간 타이밍 문제. |
| 우선순위 / 제어권 문제 | 특정 자식 요소에만 이벤트가 작동해야 할 경우(예: 보안적으로 중요한 버튼, 접근성 고려), 위임이 오히려 복잡도를 높일 수 있음. |
| 이벤트 타깃 판별의 복잡성 | event.target이 자식 요소 안쪽의 태그일 경우, closest()나 커스텀 식별자(데이터 속성, 클래스) 설정 필요. 잘못 사용하면 의도치 않은 요소가 이벤트를 갖게 됨. |
아래 상황들을 비교하며 판단하면 됩니다:
| 조건 | 위임이 적합한 경우 | 직접 등록이 나은 경우 |
|---|---|---|
| 반복적 요소 수 | 수십 ~ 수백 개 이상 반복 요소 있음 | 아주 소수이거나 단 하나뿐인 요소 |
| 동적 생성 여부 | 새로운 요소가 자주 추가/삭제됨 | 마크업이 고정되어 있고 자주 변하지 않음 |
| 이벤트 종류 | 클릭, 더블클릭, 키보드 이벤트 등 버블링 가능한 이벤트 | focus, blur, submit(폼), scroll, touchmove 등 버블링 제한 혹은 민감한 제어 필요 이벤트 |
| 예상 충돌 가능성 | 자식 간 이벤트 우선순위 충돌이 적거나 쉽게 구분 가능 | 복잡한 UI 로직, 많은 상호작용, 제어 권한 필요 시 직접 붙이는 것이 명확함 |
| 성능 요구 수준 | 저사양 환경(모바일, 오래된 브라우저), 대량 데이터 렌더링 필요 | 단순 페이지, 요소 적음, 최적화보다 명확성이 더 중요한 경우 |
data- 속성, 태그명 등을 미리 정리해두면 이벤트 타깃 판별이 쉬움closest() 와 matches() 사용법 숙지stopPropagation(), stopImmediatePropagation() 등이 이벤트 흐름을 끊는 곳이 없는지 점검링크 드라퍼는 단순한 저장 툴이 아닙니다.
정리하고, 수정하고, 다시 꺼내보게 만드는 링크 관리 도구를 지향하고 있습니다.
• 🔗 빠르고 간편한 링크 저장
• 🧠 저장한 링크를 폴더별로 정리
• 🌐 폴더를 친구에게 공유 가능
• ⚡ 크롬 익스텐션 원클릭 저장
👉 링크 드라퍼 사용하러 가기
👉 크롬 웹스토어에서 설치하기
서비스 업데이트
기능 꿀팁
카카오톡 채널을 통해 빠르게 받아보세요!
👉 카카오톡 채널 추가하기