이론에서 실전으로
지금까지 변수, 조건문, 함수, 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);
직접 해보기 ✏️
탭 컴포넌트를 직접 구현해보세요.
- "정보", "취미", "목표" 탭 3개 만들기
- 각 탭 클릭 시 해당 내용만 표시
- 활성 탭 스타일 변경 (
.active클래스) - 기본으로 첫 번째 탭 활성화
정리
| 패턴 | 용도 |
|---|---|
| 폼 유효성 검사 | 사용자 입력 실시간 검증 |
| 디바운스 | 과도한 API 호출 방지 |
| 탭 컴포넌트 | 같은 공간에서 여러 콘텐츠 전환 |
| 모달 | 팝업 다이얼로그 |
| 카운터 애니메이션 | 숫자 올라가는 효과 |
다음 편은 JavaScript 시리즈 마지막 — 완성 프로젝트입니다!