🟨 2-3. 폼(Form)과 유효성 검사(Validation) — submit, input, change 이벤트로 배우는 입력 검증의 모든 것
1. 폼 유효성 검사가 왜 중요한가
웹에서 사용자가 입력한 정보는 항상 신뢰할 수 없다.
잘못된 이메일, 빈칸, 특수문자, 중복 비밀번호…
이런 데이터를 서버에 그대로 보내면 보안과 서비스 품질 모두 떨어진다.
그래서 브라우저가 보내기 전에 클라이언트 측 검증(Client-side Validation) 을 거친다.
이는 단순히 에러를 막는 용도가 아니라,
“사용자가 실수를 덜 하게 돕는 UX 기능”이다.
2. 예시: 회원가입 폼 만들기
먼저 실습용 HTML을 구성하자.
이건 로그인, 댓글 입력 등에도 바로 응용할 수 있는 가장 기본형이다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>폼 유효성 검사</title>
<style>
body { font-family: sans-serif; margin: 40px; }
form { width: 320px; display: flex; flex-direction: column; gap: 12px; }
input { padding: 8px; border: 1px solid #ccc; border-radius: 6px; }
button { padding: 8px; background: #4a90e2; color: #fff; border: none; border-radius: 6px; cursor: pointer; }
.error { color: red; font-size: 13px; }
.success { color: green; margin-top: 10px; }
</style>
</head>
<body>
<h1>회원가입</h1>
<form id="signupForm" novalidate>
<label>
이메일
<input type="email" id="email" placeholder="example@email.com" />
<div id="emailError" class="error"></div>
</label>
<label>
비밀번호
<input type="password" id="password" placeholder="8자 이상" />
<div id="passwordError" class="error"></div>
</label>
<label>
비밀번호 확인
<input type="password" id="confirm" placeholder="비밀번호 재입력" />
<div id="confirmError" class="error"></div>
</label>
<button type="submit">가입하기</button>
</form>
<div id="result" class="success"></div>
<script type="module" src="main.js"></script>
</body>
</html>
설명:
- novalidate 속성으로 브라우저 기본 검증을 비활성화하고 JS로 제어
- 각 입력 아래에 <div class="error"> 를 만들어 에러 메시지를 표시할 공간 확보
3. 이벤트 구조 설계
우리가 사용할 이벤트는 세 가지다.
이벤트 설명 활용 목적
| input | 입력값이 바뀔 때마다 발생 | 실시간 검증 |
| change | 입력 후 포커스를 잃을 때 | 느슨한 검증 |
| submit | 폼 전송 시점 | 최종 전체 검증 |
이 세 이벤트를 적절히 섞으면
실시간 피드백 + 제출 시 최종 확인 둘 다 가능하다.
4. 자바스크립트 로직 — 유효성 검사 구현
// main.js
const form = document.getElementById("signupForm");
const emailInput = document.getElementById("email");
const passwordInput = document.getElementById("password");
const confirmInput = document.getElementById("confirm");
const emailError = document.getElementById("emailError");
const passwordError = document.getElementById("passwordError");
const confirmError = document.getElementById("confirmError");
const result = document.getElementById("result");
function validateEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
function validatePassword(pw) {
return pw.length >= 8;
}
function showError(element, message) {
element.textContent = message;
}
function clearError(element) {
element.textContent = "";
}
4-1. 실시간 이메일 검증 (input 이벤트)
emailInput.addEventListener("input", () => {
if (!validateEmail(emailInput.value)) {
showError(emailError, "유효한 이메일 주소를 입력하세요.");
} else {
clearError(emailError);
}
});
이메일이 입력될 때마다 정규식으로 즉시 검증.
4-2. 비밀번호 검증 (input 이벤트)
passwordInput.addEventListener("input", () => {
if (!validatePassword(passwordInput.value)) {
showError(passwordError, "비밀번호는 8자 이상이어야 합니다.");
} else {
clearError(passwordError);
}
});
비밀번호 길이를 실시간으로 확인하고 피드백 제공.
입력값이 길어질 때마다 에러 문구가 사라지는 식의 즉각적 UX는
사용자가 실수를 줄이게 도와준다.
4-3. 비밀번호 일치 확인 (change 이벤트)
confirmInput.addEventListener("change", () => {
if (passwordInput.value !== confirmInput.value) {
showError(confirmError, "비밀번호가 일치하지 않습니다.");
} else {
clearError(confirmError);
}
});
비밀번호 확인은 실시간보다
입력 완료(change) 시점에 검증하는 것이 일반적이다.
4-4. 폼 제출(submit) 이벤트로 최종 확인
form.addEventListener("submit", (event) => {
event.preventDefault(); // 기본 새로고침 방지
let valid = true;
if (!validateEmail(emailInput.value)) {
showError(emailError, "유효한 이메일을 입력하세요.");
valid = false;
}
if (!validatePassword(passwordInput.value)) {
showError(passwordError, "비밀번호는 8자 이상이어야 합니다.");
valid = false;
}
if (passwordInput.value !== confirmInput.value) {
showError(confirmError, "비밀번호가 일치하지 않습니다.");
valid = false;
}
if (valid) {
result.textContent = "✅ 회원가입 성공!";
form.reset();
} else {
result.textContent = "";
}
});
preventDefault() 로 폼의 기본 새로고침을 막고,
검증 통과 시 메시지를 표시하거나, 실패 시 각 필드에 피드백을 유지한다.
5. UX 개선: 실시간 하이라이트 효과 추가
조금만 다듬으면 실제 서비스 UX와 비슷해진다.
input.valid {
border-color: #4caf50;
}
input.invalid {
border-color: #f44336;
}
function toggleValidClass(input, isValid) {
input.classList.toggle("valid", isValid);
input.classList.toggle("invalid", !isValid);
}
emailInput.addEventListener("input", () => {
const ok = validateEmail(emailInput.value);
toggleValidClass(emailInput, ok);
ok ? clearError(emailError) : showError(emailError, "이메일 형식이 올바르지 않습니다.");
});
이제 입력창의 테두리 색이 실시간으로 바뀌며,
사용자가 “지금 맞게 입력하고 있는지” 즉시 피드백을 받는다.
6. 고급 예시 — 조건부 비밀번호 검증
좀 더 실전처럼 조건을 여러 개 넣을 수도 있다.
예를 들어 비밀번호는 다음을 모두 포함해야 한다면?
- 최소 8자
- 대문자 하나
- 숫자 하나
function validatePasswordComplex(pw) {
const length = pw.length >= 8;
const upper = /[A-Z]/.test(pw);
const number = /[0-9]/.test(pw);
return length && upper && number;
}
이런 함수를 기존 validatePassword 대신 적용하면 된다.
이건 은행, 공공기관, 쇼핑몰 등에서 자주 쓰이는 방식이다.
7. change vs input 의 실제 차이
이벤트 발생 시점 실시간 반응 대표 사례
| input | 입력될 때마다 | ✅ 가능 | 검색창, 이메일, 비밀번호 |
| change | 포커스 이탈 시 | ❌ 늦음 | 셀렉트박스, 체크박스 |
| submit | 폼 전체 전송 시 | 전체 확인 | 가입 버튼, 로그인 |
→ 실시간 반응이 필요한 입력은 input,
확정 후만 처리하면 되는 경우는 change를 쓰는 게 좋다.
8. 실무 확장 포인트
✅ 1) 서버 검증과 연결
- 실제 서비스에서는 JS 검증 후, 백엔드에서도 한 번 더 확인해야 한다.
- 클라이언트 검증은 UX 목적, 서버 검증은 보안 목적이다.
✅ 2) 중복 확인 기능
- 이메일 중복 검사는 input 이벤트 + debounce(지연 요청) 을 활용
- setTimeout 기반으로 “입력 멈춤 후 0.5초 뒤”에 서버 요청을 보내면 좋다.
✅ 3) 다국어 메시지 처리
- 에러 문구를 한 곳에서 관리 (예: errors.js)
- 향후 언어별로 번역 가능하게 구조화
✅ 4) 접근성 고려
- aria-live="polite" 속성으로 에러 메시지 읽히게
- 스크린 리더 사용자도 검증 결과를 들을 수 있음
9. 정리
포인트 설명
| 이벤트 | input, change, submit 의 구분이 핵심 |
| 검증 단계 | 실시간 + 최종(전송) 검증을 함께 구성 |
| UX | 입력 도중 즉시 피드백 제공 |
| 코드 구조 | “검증 함수 + 표시 함수”로 역할 분리 |
| 확장성 | 서버 검증, 중복 확인, 다국어 메시지로 발전 가능 |
10. 마무리
이제 단순히 form.submit()만 아는 게 아니라,
“입력 흐름을 설계하는 개발자” 수준으로 올라왔다.
자바스크립트의 이벤트는 데이터 흐름의 입구다.
입력 → 검증 → 피드백 → 제출,
이 모든 단계가 이벤트로 이어진다.