테마
06. 폼 유효성 검증과 FormData
브라우저는 폼에 대해 이미 많은 일을 해 준다. 문제는 그 기본 기능을 모르고 전부 직접 다시 짜는 순간부터 시작된다.
학습 목표
submit()과requestSubmit()의 차이를 설명할 수 있다.- Constraint Validation API의 핵심 메서드와 상태를 이해한다.
SubmitEvent.submitter,FormData,formdata이벤트의 역할을 설명할 수 있다.- 폼 처리에서 브라우저 기본 기능과 커스텀 로직을 어떻게 나눌지 판단할 수 있다.
1. 폼은 아직도 브라우저의 강한 기본 기능이다
단순해 보이지만 폼은 아래를 기본 제공한다.
- 입력 필드 그룹화
- Enter 제출
- 제출 버튼 식별
- 기본 유효성 검사
- URL 인코딩 또는 multipart 전송
현대 프레임워크에서도 이 기능을 완전히 버릴 이유는 많지 않다.
오히려 브라우저 기본을 적절히 살리고, 필요한 부분만 덧붙이는 편이 더 단단하다.
2. submit()과 requestSubmit()은 다르다
이 차이는 반드시 알고 있어야 한다.
| 메서드 | 유효성 검사 | submit 이벤트 발생 | 사용 추천도 |
|---|---|---|---|
form.submit() | 하지 않음 | 발생하지 않음 | 낮음 |
form.requestSubmit() | 수행함 | 발생함 | 높음 |
즉, "사용자가 제출 버튼을 눌렀을 때와 비슷하게" 동작시키고 싶다면 requestSubmit()이 맞다.
원문이 이 차이를 짚은 점은 좋고, 현재 기준에서는 이 메서드의 우선순위를 더 높게 두면 된다.
3. 브라우저 기본 유효성 검사를 먼저 활용하자
Constraint Validation API는 복잡해 보이지만 핵심은 단순하다.
- HTML 속성으로 기본 규칙 정의
- 브라우저가 검사
- 필요할 때 JavaScript로 상태 확인이나 사용자 메시지 보강
대표 속성은 아래와 같다.
requiredmin,max,stepminlength,maxlengthpatterntype="email",type="url"등
기본 규칙만으로도 많은 검증을 브라우저에게 맡길 수 있다.
4. 자주 쓰는 검증 메서드
| 메서드 | 역할 |
|---|---|
checkValidity() | 유효하면 true, 아니면 false 반환 |
reportValidity() | 검사 후 오류 UI도 표시 |
setCustomValidity(message) | 사용자 정의 오류 메시지 설정 |
또 함께 봐야 할 것이 validity다.
ValidityState 안에는 아래 같은 상태가 담긴다.
valueMissingtypeMismatchpatternMismatchrangeUnderflowrangeOverflowtooShorttooLong
실무에서는 "어떤 규칙이 깨졌는가"를 이 객체로 판별해 사용자 메시지를 구체화할 수 있다.
5. invalid 이벤트와 사용자 경험
폼 검증은 단순히 막는 것이 아니라, 사용자가 어디를 어떻게 고치면 되는지 알려 주는 문제다.
좋은 흐름은 보통 이렇다.
- 사용자가 제출 시도
- 브라우저 기본 검증 실행
- 첫 오류 필드로 포커스 이동
- 필요하면 설명 텍스트를 추가
너무 이른 실시간 검증은 오히려 UX를 망칠 수 있다.
예를 들어 사용자가 아직 이메일 입력을 끝내지 않았는데 빨간 경고를 계속 띄우면 피로감만 커진다.
6. SubmitEvent.submitter는 실무에서 꽤 유용하다
폼에 제출 버튼이 여러 개 있을 수 있다.
- 임시 저장
- 게시
- 미리보기
이때 submit 이벤트 안에서 event.submitter를 보면 어떤 버튼이 제출을 트리거했는지 알 수 있다.
js
form.addEventListener('submit', (event) => {
event.preventDefault()
const mode = event.submitter?.value
console.log(mode)
})이 기능을 알면 "버튼마다 별도 폼 만들기" 같은 비효율을 줄일 수 있다.
7. FormData는 서버 전송 직전의 구조화된 스냅샷이다
js
const formData = new FormData(form)
await fetch('/signup', {
method: 'POST',
body: formData,
})장점은 분명하다.
- 폼 필드
name과 값이 자동 수집된다 - 파일 업로드도 함께 다룰 수 있다
append,set,delete,get,getAll로 조작할 수 있다
즉, 직접 JSON 객체를 새로 조립하기 전에 이미 폼이라는 좋은 소스가 있다면 FormData부터 검토할 가치가 크다.
8. formdata 이벤트는 전송 직전 개입 지점이다
브라우저가 FormData를 구성하는 시점에 맞춰 개입할 수 있다.
- 숨겨진 메타데이터 추가
- 체크박스 묶음 값 정리
- 사용자 편집값 외의 보조 정보 합치기
이 패턴을 알면 HTML 폼 구조를 유지하면서도 전송 직전 데이터를 유연하게 가공할 수 있다.
9. 비동기 제출과 기본 제출을 어떻게 나눌까?
현대 앱에서는 fetch 기반 비동기 제출이 흔하다.
그래도 기본 제출 흐름을 버릴 필요는 없다.
기본 제출이 좋은 경우
- 단순 검색 폼
- 페이지 이동이 자연스러운 흐름
- 복잡한 상태 관리가 불필요
비동기 제출이 좋은 경우
- 제출 후 현재 화면 유지
- 실시간 피드백과 부분 갱신
- 업로드 진행 표시, 후속 액션 연계
핵심은 둘 중 하나를 맹목적으로 고르는 것이 아니라, 브라우저 기본을 얼마나 살릴지 판단하는 것이다.
10. 폼 연동 커스텀 엘리먼트도 가능하다
원문 후반의 ElementInternals까지 연결하면, 커스텀 엘리먼트도 폼과 자연스럽게 통합할 수 있다.
- 폼 제출 데이터에 값 포함
- 유효성 상태 연동
- Shadow DOM 내부 구현을 유지하면서 외부 폼과 결합
다만 이 부분은 일반 폼보다 난도가 높다.
기본 폼 처리 흐름을 먼저 확실히 이해한 뒤 Web Components 장에서 읽는 편이 좋다.
11. 흔한 안티패턴
| 안티패턴 | 문제점 | 더 나은 방식 |
|---|---|---|
무조건 form.submit() 호출 | 검증과 submit 이벤트를 건너뜀 | 기본은 requestSubmit() |
| 브라우저 검증을 전부 비활성화 | 구현량 증가, 접근성 저하 가능 | 네이티브 검증을 최대한 활용 |
| 입력 중 매 순간 공격적 에러 표시 | 피로감 증가 | 제출 시점 또는 적절한 타이밍 검증 |
| 폼 데이터 전부 수동 JSON 조립 | 중복 작업 발생 | 가능하면 FormData 활용 |
12. PR 리뷰 체크리스트
- 제출 트리거가
requestSubmit()이 되어야 할 상황은 아닌가 - 기본 유효성 검사 속성을 HTML에서 먼저 표현하고 있는가
- 오류 메시지와 포커스 이동이 사용자에게 충분히 친절한가
- 여러 제출 버튼이 있다면
event.submitter를 활용하고 있는가 - 비동기 제출 시에도 브라우저 기본 폼 의미를 최대한 살리고 있는가
핵심 정리
- 폼은 아직도 브라우저가 가장 잘하는 영역 중 하나다
requestSubmit()과 Constraint Validation API를 알면 불필요한 재구현을 크게 줄일 수 있다FormData와submitter를 활용하면 현대적인 비동기 폼 처리도 더 깔끔하게 설계할 수 있다