기본기에 대하여
특히 처음에는, 곧바로 작동하는 부분으로 건너뛰고 싶은 유혹이 있다. 라이브러리를 가져오고, 스니펫을 복사하고, 데모를 돌린다. 그렇게 하면 빠르게 나아간다 — 막히기 전까지는. 행복한 경로(happy path)를 벗어나는 순간, 받아 든 에러는 추상화 세 겹 아래에 있고, 무엇이 왜 실패하는지 전혀 알 수 없을 때까지는.
나도 그런 사람이었다. 작동하는 코드를 손에 쥐고도 왜 작동하는지는 전혀 모르는 기분을 안다. 그 불편함은 사라지지 않는다 — 일이 매끄럽게 풀릴 때 잠시 조용해질 뿐, 어긋나는 순간 아주 시끄러워진다.
가장 아래 층이 중요한 이유
무언가가 무엇 위에 세워졌는지 이해하면, 에러에 주소가 생긴다. "이 콜백이 실행되지 않는다"는 말은, 이벤트 루프가 실제로 무엇을 하는지 알 때 다른 의미를 갖는다. "컴포넌트가 너무 자주 리렌더링된다"는, React가 리렌더링을 어떻게 결정하는지 이해할 때 진단 가능해진다. 추상화는 여전히 유용하다 — 가상 DOM을 직접 구현해야 쓸 수 있는 건 아니다 — 하지만 그 아래에서 무슨 일이 일어나는지 알면, 그것이 언제 깨지고 어디를 봐야 하는지 알 수 있다.
목표는 전지전능이 아니다. 일이 어긋났을 때 쓸모 있을 만큼 정확한 멘탈 모델을 세우는 것이다.
구체적인 예: 이벤트 위임
기초를 아는 것이 모든 것을 바꾸는 전형적인 사례. addEventListener가 요소에 핸들러를 붙인다는 것만 안다면, 반복문 안에서 항목마다 핸들러를 붙이는 게 괜찮아 보인다 — 목록이 커지거나 항목이 동적으로 추가·제거되기 전까지는. 하지만 클릭 이벤트가 DOM을 타고 위로 버블링된다는 걸 안다면, 부모에 하나의 핸들러만 붙이고 이벤트가 찾아오게 둘 수 있다.
// 항목마다 핸들러 하나 — 목록이 동적이면 무너진다
items.forEach((item) => {
item.addEventListener("click", handleClick);
});
// 부모에 핸들러 하나 — 이벤트가 도착하면 target을 읽는다
list.addEventListener("click", (e) => {
const item = e.target.closest("[data-item]");
if (!item) return;
handleClick(item);
});작고 고정된 목록이라면 어느 쪽도 틀리지 않았다. 하지만 크고 동적인 목록에서는 두 번째가 압도적으로 저렴하고, 그 이유는 버블링을 이해해야만 보인다. 그것이 기초를 배우는 것의 수익이다: 최적화는 필요해지기 전까지 보이지 않다가, 필요한 순간 정확히 그것이 답이 된다.
맺으며
의도적으로 천천히 가라. 속도가 중요하지 않아서가 아니라, 이해는 복리로 쌓이기 때문이다. 진짜로 이해한 개념 하나하나가 만료되지 않는 도구가 된다. 훑고 지나가면 흐려지고, 직접 쌓으면 남는다.
다시, 더 똑똑하게. 🔁