Skip to Content
WebJavaScriptWeb APIs06. 폼과 검증

이번 편의 결과물: 05편의 인라인 입력 칸 두 개를 <form>으로 묶고, 제목이 없거나 너무 짧으면 저장을 막고 필드 아래에 오류 메시지를 보여줍니다. · 다루는 개념: <form>/FormData, required/minlength와 Constraint Validation API, 커스텀 에러 메시지 표시

이 편에서 만드는 파일

notes-app/ ├── index.html (~ 인라인 추가 영역을 <form>으로 교체) └── src/ └── main.js (~ FormData·Constraint Validation API 적용)

개념 정리

form과 FormData

<form>은 입력 요소들을 하나로 묶고, submit 이벤트로 “제출”이라는 명확한 시점을 만듭니다. new FormData(form)은 폼 안의 name 속성이 붙은 입력값을 한 번에 모아 줍니다. 입력마다 변수를 따로 읽지 않아도 됩니다.

const formData = new FormData(form); formData.get('title'); // input name="title"의 값

Constraint Validation API

브라우저는 required, minlength, pattern 같은 속성만으로 기본 검증을 해 줍니다. 자바스크립트에서는 다음 메서드·프로퍼티로 이 검증에 개입합니다.

이름역할
input.checkValidity()그 입력 하나가 규칙을 만족하는지 true/false로 반환
form.checkValidity()폼 안의 모든 입력을 검사
input.setCustomValidity(msg)메시지를 넣으면 강제로 무효 상태로 만듦. 빈 문자열을 넣으면 다시 유효 상태로 돌아감
input.validationMessage현재 무효 사유 메시지(브라우저 기본 문구 또는 setCustomValidity로 넣은 문구)
form.reportValidity()무효한 입력에 브라우저 기본 말풍선을 표시

왜 직접 오류 메시지를 그리는가

브라우저 기본 말풍선(reportValidity)은 스타일을 맞추기 어렵습니다. 그래서 실무에서는 novalidate로 브라우저의 자동 검사를 끄지 않고 그대로 두되, 검증 결과를 직접 읽어 필드 아래에 문구를 그리는 방식을 함께 씁니다.

실습

1. index.html 열기

05편에서 만든 .note-composer 영역을 <form>으로 바꿉니다.

2. 코드 작성

<!-- index.html --> <form id="note-form" class="note-composer" novalidate> <div class="field"> <label for="new-title">제목</label> <input id="new-title" name="title" type="text" required minlength="2" /> <p class="field-error" data-error-for="title"></p> </div> <div class="field"> <label for="new-body">내용</label> <input id="new-body" name="body" type="text" /> </div> <button type="submit">추가</button> </form>

novalidate는 폼 제출 시 브라우저가 말풍선을 자동으로 띄우는 것만 막습니다. required, minlength 속성 자체와 checkValidity()의 판정 결과는 그대로 유지됩니다.

src/main.js에서 addBtn 클릭 리스너를 지우고 submit 리스너로 바꿉니다.

// src/main.js import { getNotes, addNote, removeNote, updateNote } from './notes.js'; import { renderList } from './render.js'; const listEl = document.querySelector('#note-list'); const form = document.querySelector('#note-form'); const titleInput = form.elements.title; const titleError = form.querySelector('[data-error-for="title"]'); let editingId = null; function render() { renderList(getNotes(), listEl, { editingId }); } titleInput.addEventListener('input', () => { titleInput.setCustomValidity(''); }); form.addEventListener('submit', (event) => { event.preventDefault(); if (titleInput.value.trim().length === 0) { titleInput.setCustomValidity('제목을 입력하세요.'); } else if (titleInput.value.trim().length < 2) { titleInput.setCustomValidity('제목은 2자 이상 입력하세요.'); } else { titleInput.setCustomValidity(''); } if (!form.checkValidity()) { titleError.textContent = titleInput.validationMessage; return; } titleError.textContent = ''; const formData = new FormData(form); addNote({ title: formData.get('title').trim(), body: formData.get('body').trim() }); form.reset(); render(); }); listEl.addEventListener('click', (event) => { const actionEl = event.target.closest('[data-action]'); if (!actionEl) return; const card = actionEl.closest('.note-card'); const id = card.dataset.id; const action = actionEl.dataset.action; if (action === 'delete') { removeNote(id); render(); } else if (action === 'edit') { editingId = id; render(); } else if (action === 'cancel') { editingId = null; render(); } else if (action === 'save') { const title = card.querySelector('.edit-title').value.trim(); const body = card.querySelector('.edit-body').value.trim(); updateNote(id, { title, body }); editingId = null; render(); } }); render();

titleInputinput 이벤트에서 매번 setCustomValidity('')로 초기화하는 이유는, 이전 제출에서 남긴 무효 상태를 지우지 않으면 사용자가 값을 고쳐도 checkValidity()가 계속 false를 반환하기 때문입니다.

3. 실행

npm run dev

확인

  • 제목 칸을 비운 채 추가를 누르면 “제목을 입력하세요.”가 칸 아래에 뜨고 목록에는 추가되지 않습니다.
  • 제목에 한 글자만 입력하면 “제목은 2자 이상 입력하세요.”가 뜹니다.
  • 제목을 2글자 이상 입력하면 정상적으로 추가되고, 오류 문구는 사라집니다.
  • 카드 안의 인라인 편집(저장)은 이번 편에서 검증을 적용하지 않았습니다. 05편과 동일하게 빈 값도 저장됩니다.

직접 해보기

  1. 내용 칸에도 maxlength="200" 속성을 추가하고, 200자를 넘기면 “내용은 200자 이하로 입력하세요.”를 보여줘 봅니다.
  2. 카드 인라인 편집의 저장에도 제목이 비어 있으면 window.alert로 막는 검증을 추가해 봅니다.

정답 보기

<input id="new-body" name="body" type="text" maxlength="200" />
} else if (action === 'save') { const title = card.querySelector('.edit-title').value.trim(); if (!title) { window.alert('제목을 입력하세요.'); return; } const body = card.querySelector('.edit-body').value.trim(); updateNote(id, { title, body }); editingId = null; render(); }

자주 하는 실수

증상원인고치는 법
오류 문구가 한 번 뜨면 값을 고쳐도 사라지지 않음setCustomValidity로 넣은 메시지를 지우지 않음input 이벤트에서 setCustomValidity('')로 매번 초기화한다
novalidate를 뺐더니 오류 문구가 브라우저 말풍선과 같이 두 번 보임novalidate 없이 커스텀 오류까지 직접 그림novalidate로 브라우저 자동 말풍선을 끄고 직접 그린 문구만 사용한다
formData.get('title')nullinputname 속성이 없음FormDataname 속성 기준으로 값을 모으므로 반드시 붙인다

확인 문제

문제 14지선다
FormData 객체를 사용하는 목적으로 가장 적절한 것은?
문제 24지선다
input.setCustomValidity('')처럼 빈 문자열을 넣으면 어떻게 되는가?
문제 34지선다
form에 novalidate 속성을 붙이는 이유로 가장 적절한 것은?
문제 44지선다
제목 칸에 한 글자만 입력하고 추가를 눌렀을 때 이 코드의 동작은?

참고 자료

Last updated on