JavaScriptJavaScript 기초 · 9중급

실전 패턴 — 자주 쓰는 JavaScript 기법 모음

JavaScript실전폼검증디바운스패턴

이론에서 실전으로

지금까지 변수, 조건문, 함수, DOM, 이벤트, 배열, fetch를 배웠습니다. 이번 편에서는 이것들을 조합해 실제 서비스에서 자주 쓰는 패턴들을 배웁니다.


패턴 1: 폼 유효성 검사

<form id="signup-form">
  <input type="text"  id="username"  placeholder="아이디 (4~12자)" />
  <span id="username-msg" class="msg"></span>

  <input type="password" id="password" placeholder="비밀번호 (8자 이상)" />
  <span id="password-msg" class="msg"></span>

  <button type="submit">가입하기</button>
</form>
const form = document.querySelector("#signup-form");

function showMsg(id, text, isValid) {
  const msg = document.querySelector(`#${id}-msg`);
  msg.textContent = text;
  msg.style.color = isValid ? "green" : "red";
}

function validateUsername(value) {
  if (value.length < 4) return { ok: false, text: "4자 이상 입력하세요." };
  if (value.length > 12) return { ok: false, text: "12자 이하로 입력하세요." };
  if (!/^[a-zA-Z0-9]+$/.test(value)) return { ok: false, text: "영문/숫자만 가능합니다." };
  return { ok: true, text: "사용 가능한 아이디입니다." };
}

function validatePassword(value) {
  if (value.length < 8) return { ok: false, text: "8자 이상 입력하세요." };
  return { ok: true, text: "안전한 비밀번호입니다." };
}

// 실시간 유효성 검사
document.querySelector("#username").addEventListener("input", (e) => {
  const result = validateUsername(e.target.value);
  showMsg("username", result.text, result.ok);
});

document.querySelector("#password").addEventListener("input", (e) => {
  const result = validatePassword(e.target.value);
  showMsg("password", result.text, result.ok);
});

// 폼 제출 시 최종 검사
form.addEventListener("submit", (e) => {
  e.preventDefault();
  const username = document.querySelector("#username").value;
  const password = document.querySelector("#password").value;

  const usernameValid = validateUsername(username);
  const passwordValid = validatePassword(password);

  if (!usernameValid.ok || !passwordValid.ok) {
    alert("입력값을 확인해주세요.");
    return;
  }

  alert("가입 완료!");
});

패턴 2: 디바운스 (Debounce)

사용자가 타이핑할 때마다 검색 요청을 보내면 서버에 부담이 됩니다. 일정 시간이 지난 후에만 실행하는 패턴입니다.

function debounce(fn, delay) {
  let timer;
  return (...args) => {
    clearTimeout(timer);
    timer = setTimeout(() => fn(...args), delay);
  };
}

// 검색 함수
async function search(query) {
  if (!query.trim()) return;
  console.log(`"${query}" 검색 중...`);
  // 실제로는 fetch 호출
}

// 0.3초 후에만 실행
const debouncedSearch = debounce(search, 300);

document.querySelector("#search-input").addEventListener("input", (e) => {
  debouncedSearch(e.target.value);
});

패턴 3: 탭 컴포넌트

<div class="tabs">
  <button class="tab active" data-tab="1">소개</button>
  <button class="tab" data-tab="2">경력</button>
  <button class="tab" data-tab="3">연락처</button>
</div>

<div class="tab-content active" id="tab-1">소개 내용입니다.</div>
<div class="tab-content" id="tab-2">경력 내용입니다.</div>
<div class="tab-content" id="tab-3">연락처 내용입니다.</div>
.tab-content { display: none; }
.tab-content.active { display: block; }
.tab.active { background: #0070f3; color: white; }
const tabs = document.querySelectorAll(".tab");
const contents = document.querySelectorAll(".tab-content");

tabs.forEach(tab => {
  tab.addEventListener("click", () => {
    const target = tab.dataset.tab;

    // 모든 탭/콘텐츠 비활성화
    tabs.forEach(t => t.classList.remove("active"));
    contents.forEach(c => c.classList.remove("active"));

    // 선택된 탭/콘텐츠 활성화
    tab.classList.add("active");
    document.querySelector(`#tab-${target}`).classList.add("active");
  });
});

패턴 4: 모달 (팝업)

<button id="open-modal">모달 열기</button>

<div id="modal" class="modal-overlay" hidden>
  <div class="modal-box">
    <h2>알림</h2>
    <p>모달 내용입니다.</p>
    <button id="close-modal">닫기</button>
  </div>
</div>
.modal-overlay {
  position: fixed;
  inset: 0;
  background: rgba(0,0,0,0.5);
  display: flex;
  justify-content: center;
  align-items: center;
}

.modal-box {
  background: white;
  padding: 32px;
  border-radius: 12px;
  max-width: 400px;
  width: 90%;
}
const modal = document.querySelector("#modal");

document.querySelector("#open-modal").addEventListener("click", () => {
  modal.hidden = false;
});

document.querySelector("#close-modal").addEventListener("click", () => {
  modal.hidden = true;
});

// 오버레이 클릭 시 닫기
modal.addEventListener("click", (e) => {
  if (e.target === modal) modal.hidden = true;
});

// ESC 키로 닫기
document.addEventListener("keydown", (e) => {
  if (e.key === "Escape") modal.hidden = true;
});

패턴 5: 숫자 애니메이션 (카운터)

<span class="counter" data-target="1000">0</span>
<span class="counter" data-target="500">0</span>
function animateCounter(element) {
  const target = parseInt(element.dataset.target);
  const duration = 1500;
  const start = performance.now();

  function update(now) {
    const elapsed = now - start;
    const progress = Math.min(elapsed / duration, 1);
    const eased = 1 - Math.pow(1 - progress, 3);  // ease-out
    element.textContent = Math.floor(eased * target).toLocaleString();

    if (progress < 1) requestAnimationFrame(update);
  }

  requestAnimationFrame(update);
}

document.querySelectorAll(".counter").forEach(animateCounter);

직접 해보기 ✏️

탭 컴포넌트를 직접 구현해보세요.

  1. "정보", "취미", "목표" 탭 3개 만들기
  2. 각 탭 클릭 시 해당 내용만 표시
  3. 활성 탭 스타일 변경 (.active 클래스)
  4. 기본으로 첫 번째 탭 활성화

정리

패턴용도
폼 유효성 검사사용자 입력 실시간 검증
디바운스과도한 API 호출 방지
탭 컴포넌트같은 공간에서 여러 콘텐츠 전환
모달팝업 다이얼로그
카운터 애니메이션숫자 올라가는 효과

다음 편은 JavaScript 시리즈 마지막 — 완성 프로젝트입니다!

궁금한 점이 있으신가요?

협업·의뢰는 아래로, 가벼운 소통은 인스타그램 @bluefox._.hi도 환영이에요.