Lesson 15

이벤트 처리

3 min Views 1

addEventListener의 동작 원리와 이벤트 객체, 버블링/캡처링, 이벤트 위임 패턴을 이해한다. 폼 이벤트, 키보드/마우스 이벤트, 사용자 경험을 높이는 이벤트 처리 패턴을 익힌다.

이벤트 — 사용자 행동에 반응하기

이벤트는 브라우저나 사용자가 발생시키는 신호입니다. 클릭, 키 입력, 스크롤, 폼 제출 등이 모두 이벤트입니다. 자바스크립트는 이벤트를 감지하고 그에 맞는 함수(이벤트 핸들러)를 실행합니다.

addEventListener 기본

const button = document.querySelector("#btn");

// 기본 방식
button.addEventListener("click", function(event) {
  console.log("클릭됨!", event);
});

// 화살표 함수
button.addEventListener("click", (e) => {
  console.log(e.target); // 클릭된 요소
});

// 이벤트 제거 (익명 함수는 제거 불가!)
function handleClick(e) {
  console.log("클릭");
}
button.addEventListener("click", handleClick);
button.removeEventListener("click", handleClick); // 제거 가능

// 한 번만 실행
button.addEventListener("click", handleClick, { once: true });

이벤트 객체

document.addEventListener("click", (e) => {
  e.target           // 실제 클릭된 요소
  e.currentTarget    // 이벤트 핸들러가 달린 요소
  e.type             // "click"
  e.clientX, e.clientY  // 마우스 좌표 (뷰포트 기준)
  e.pageX, e.pageY      // 마우스 좌표 (문서 기준)
  e.key              // 키보드: "Enter", "Escape" 등
  e.code             // 키보드: "KeyA", "Space" 등
  e.ctrlKey          // Ctrl 키 눌렸는지
  e.shiftKey         // Shift 키 눌렸는지
  e.altKey           // Alt 키 눌렸는지

  e.preventDefault() // 기본 동작 방지
  e.stopPropagation() // 버블링 중단
});

이벤트 버블링과 캡처링

이벤트는 세 단계로 흐릅니다: 캡처링(위→아래) → 타겟 → 버블링(아래→위).

<!-- HTML 구조 -->
<div id="outer">
  <div id="inner">
    <button id="btn">클릭</button>
  </div>
</div>

// 버블링 (기본): 이벤트가 btn → inner → outer 순으로 전파
document.querySelector("#btn").addEventListener("click", e => {
  console.log("btn 클릭");
});
document.querySelector("#inner").addEventListener("click", e => {
  console.log("inner도 실행됨"); // 버블링으로 실행!
});

// 버블링 막기
btn.addEventListener("click", e => {
  e.stopPropagation();
  console.log("btn만 실행됨");
});

// 캡처링 단계에 등록
outer.addEventListener("click", handler, { capture: true }); // 먼저 실행

이벤트 위임 — 핵심 패턴

100개 버튼마다 핸들러를 달지 않고, 부모 하나에 달아서 처리합니다. 동적으로 추가되는 요소에도 자동으로 동작합니다.

// 나쁜 방법: 각 요소마다 핸들러 등록
document.querySelectorAll(".delete-btn").forEach(btn => {
  btn.addEventListener("click", deleteItem);
});
// 새로 추가된 버튼에는 동작하지 않음!

// 좋은 방법: 이벤트 위임
document.querySelector("#list").addEventListener("click", (e) => {
  // 실제 클릭된 요소 확인
  if (e.target.matches(".delete-btn")) {
    const id = e.target.dataset.id;
    deleteItem(id);
  }
  if (e.target.matches(".edit-btn")) {
    editItem(e.target.dataset.id);
  }
});

// closest — 가장 가까운 조상 찾기
document.querySelector("#list").addEventListener("click", (e) => {
  const item = e.target.closest(".list-item");
  if (!item) return; // 아이템 밖 클릭 무시
  const id = item.dataset.id;
  handleItemClick(id);
});

폼 이벤트

const form = document.querySelector("#signup-form");

// submit — 폼 제출
form.addEventListener("submit", (e) => {
  e.preventDefault(); // 페이지 새로고침 방지!
  const data = new FormData(form);
  const name = data.get("name");
  const email = data.get("email");
  submitForm({ name, email });
});

// input — 값이 변할 때마다 (실시간)
const input = document.querySelector("#search");
input.addEventListener("input", (e) => {
  search(e.target.value);
});

// change — 입력 완료 후 (포커스 아웃 시)
const select = document.querySelector("#category");
select.addEventListener("change", (e) => {
  filterByCategory(e.target.value);
});

// focus / blur
input.addEventListener("focus", () => input.classList.add("focused"));
input.addEventListener("blur", () => input.classList.remove("focused"));

키보드 이벤트

document.addEventListener("keydown", (e) => {
  if (e.key === "Escape") closeModal();
  if (e.key === "Enter" && e.ctrlKey) submitForm();
  if (e.key === "s" && (e.ctrlKey || e.metaKey)) {
    e.preventDefault(); // 브라우저 저장 방지
    saveDocument();
  }
});

// 입력 필드에서 Enter 처리
const input = document.querySelector("#input");
input.addEventListener("keydown", (e) => {
  if (e.key === "Enter") {
    e.preventDefault();
    addItem(input.value);
    input.value = "";
  }
});

디바운스 — 연속 이벤트 최적화

// 검색창에 타이핑할 때마다 API 호출하면 너무 많은 요청 발생
// 디바운스: 마지막 입력 후 일정 시간 지나면 한 번만 실행
function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

const handleSearch = debounce((value) => {
  fetchSearchResults(value);
}, 300);

input.addEventListener("input", (e) => handleSearch(e.target.value));
// 타이핑을 멈춘 뒤 300ms 후에 한 번만 API 호출

관련 검색어  ·  addEventListener 사용법, 이벤트 위임 패턴, 이벤트 버블링, preventDefault, 폼 이벤트, 디바운스 구현

채용정보
공지사항
인사이트
MY