JS LAB
JS 4.7 · 4부. 브라우저와 DOM

폼 입력과 이벤트 위임

입력값을 읽고 검사합니다. 위임으로 여러 요소를 한 번에 다룹니다.

예상 학습 시간 16분 실행 단추가 있는 예제는 고쳐서 실행해 볼 수 있습니다 난이도 중급
개념 설명

입력을 받고, 늘어나는 요소를 다룹니다

이 단원에서 두 가지를 다룹니다. 입력 칸에서 값을 읽어 검사하는 것과, 나중에 만들어질 요소까지 한 번에 다루는 것입니다.

두 번째가 이벤트 위임 입니다. 4.6 처럼 항목을 계속 만들어 넣는 화면에서는 만들 때마다 이벤트를 붙일 수 없습니다.

최소 예제 · 편집 가능

입력값 읽고 검사하기

script.js
const input = document.querySelector("#name");
const add = document.querySelector("#add");
const msg = document.querySelector("#msg");

add.addEventListener("click", () => {
	const value = input.value.trim();

	if (value === "") {
		msg.textContent = "이름을 입력해 주세요";
		return;
	}

	msg.textContent = `${value}님 안녕하세요`;
	input.value = "";
});

add.click();
console.log(msg.textContent);

input.value = "  홍길동  ";
add.click();
console.log(msg.textContent);
출력
이름을 입력해 주세요 홍길동님 안녕하세요

value 는 언제나 글자입니다. 앞뒤 빈칸을 trim 으로 걷어내야 빈칸만 넣은 것을 걸러 낼 수 있습니다(1.5).

맞지 않으면 return 으로 그 자리에서 끝냅니다. else 로 감싸는 것보다 읽기 좋습니다(7.3 에서 다시 다룹니다).

상세 사용법

왜 위임이 필요한가

아래는 동작하지 않는 코드입니다. 이벤트를 붙일 때 있던 항목에만 붙었고, 뒤에 만든 항목에는 붙지 않았습니다.

script.js
const list = document.querySelector("#list");

document.querySelectorAll("#list button").forEach(btn => {
	btn.addEventListener("click", () => console.log("지웁니다"));
});

const li = document.createElement("li");
li.innerHTML = "<button>지우기</button> 공책";
list.append(li);

list.querySelectorAll("button").forEach(btn => btn.click());
출력
지웁니다

단추가 둘인데 한 번만 나왔습니다. 나중에 만든 단추에는 이벤트가 없기 때문입니다.

상세 사용법

부모 하나에만 붙입니다

누른 자리에서 일어난 일은 부모에게도 전해집니다. 그래서 부모에 한 번만 붙여 두고, event.target 으로 어디를 눌렀는지 가리면 됩니다(4.5).

script.js
const list = document.querySelector("#list");

list.addEventListener("click", event => {
	const btn = event.target.closest("button");
	if (!btn) return;

	const li = btn.closest("li");
	console.log(`${li.querySelector(".name").textContent} 지웠습니다`);
	li.remove();
});

list.querySelector("button").click();
console.log(list.children.length);
출력
연필 지웠습니다 1

closest 는 자기 자신부터 위로 올라가며 선택자에 맞는 가장 가까운 조상을 찾습니다. 단추 안의 글자를 눌러도 단추를 찾아 줍니다.

if (!btn) return; 이 중요합니다. 목록의 빈 자리를 눌렀을 때는 아무 일도 하지 않아야 합니다.

상세 사용법

폼으로 받기

입력 칸을 form 으로 감싸면 엔터로도 보낼 수 있습니다. 다만 그대로 두면 화면이 새로 뜨므로 preventDefault 가 필요합니다(4.5).

script.js
const form = document.querySelector("#form");

form.addEventListener("submit", event => {
	event.preventDefault();

	const value = form.querySelector("input").value.trim();
	if (value === "") return;

	// 여기서 항목을 더합니다(4.6)
});

이 예제는 실행 단추가 없습니다. 폼을 보내는 것은 화면을 옮기는 일이라 실행 자리에서 막혀 있습니다. 실제 페이지에서는 위 코드가 그대로 동작합니다.

흔한 실수

처음에 자주 걸리는 것

1. 만들 때마다 이벤트를 붙입니다

항목이 늘어날수록 이벤트도 늘어납니다. 부모에 한 번만 붙이는 위임이 간단하고 빠릅니다.

2. event.target 을 그대로 사용합니다

단추 안의 글자나 아이콘을 누르면 target 이 그것이 됩니다. closest 로 원하는 요소까지 올라가세요.

3. 못 찾았을 때를 걸러 내지 않습니다

closestnull 을 돌려주면 이어지는 줄에서 오류가 납니다. if (!btn) return; 을 먼저 두세요.

TypeError: Cannot read properties of null (reading 'closest')
실습 문제

파트 4 미니 실습 — To-Do 목록

4부에서 배운 것을 모아 봅니다 난이도 상

입력 칸·추가 단추·목록이 있습니다. 아래 넷을 모두 만드세요.

  • 추가 단추를 누르면 입력한 글로 항목을 만들어 넣습니다(4.6).
  • 빈칸만 넣었으면 더하지 않습니다(trim).
  • 항목의 글자를 누르면 done class 를 켜고 끕니다(4.4).
  • 항목의 지우기를 누르면 그 항목을 지웁니다.

셋째와 넷째는 나중에 만들어진 항목에도 동작해야 합니다 — 위임입니다.

목록에 click 을 한 번만 붙이고, 그 안에서 closest("button") 인지 closest(".name") 인지로 갈라 처리하면 됩니다.
const input = document.querySelector("#todo"); const add = document.querySelector("#add"); const list = document.querySelector("#list"); add.addEventListener("click", () => { const text = input.value.trim(); if (text === "") return; const li = document.createElement("li"); const name = document.createElement("span"); name.className = "name"; name.textContent = text; const del = document.createElement("button"); del.textContent = "지우기"; li.append(name, del); list.append(li); input.value = ""; }); list.addEventListener("click", event => { if (event.target.closest("button")) { event.target.closest("li").remove(); return; } const name = event.target.closest(".name"); if (name) name.classList.toggle("done"); });
script.js
// 위 정답을 여기에 옮겨 적고 실행해 보세요.
// 실행한 뒤 미리보기 탭에서 직접 더하고 눌러 볼 수 있습니다.
console.log("여기에 작성하세요");
출력
여기에 작성하세요

여기까지 하면 파트 4 가 끝납니다. 다음 파트에서는 서버에서 데이터를 가져옵니다.

요약

이 단원의 정리

  • 입력 칸의 value 는 언제나 글자입니다. trim 으로 다듬어 검사합니다.
  • 맞지 않으면 return 으로 먼저 끝냅니다.
  • 나중에 만들어질 요소까지 다루려면 부모에 한 번만 붙입니다(위임).
  • event.target.closest 로 원하는 요소까지 올라갑니다.
  • 못 찾았을 때(null)를 먼저 걸러 냅니다.