HTML Form의 Submit
작성일 : 2025.03.12
HTML 폼(Form)은 웹사이트에서 사용자 입력을 수집하는 핵심 도구입니다. 폼의 최종 목적지는 대개 '제출(Submit)'입니다. 이 과정은 겉보기에 단순해 보이지만, 내부적으로는 복잡한 메커니즘이 작동하고 있으며 다양한 방식으로 구현할 수 있습니다. 이 글에서는 HTML 폼 제출의 모든 것을 자세히 알아보겠습니다.
기본 개념: HTML Form과 Submit
HTML 폼은 <form> 태그로 시작하여 내부에 다양한 입력 요소(input, select, textarea 등)를 포함합니다. 폼 제출(submit)은 사용자가 입력한 데이터를 서버로 전송하는 행위를 말합니다.
가장 기본적인 형태의 HTML 폼은 다음과 같습니다:
<form action="/submit-url" method="post">
<label for="name">이름:</label>
<input type="text" id="name" name="name">
<label for="email">이메일:</label>
<input type="email" id="email" name="email">
<input type="submit" value="제출하기">
</form>
이 폼이 제출되면 사용자가 입력한 이름과 이메일 데이터가 /submit-url 경로로 POST 방식을 통해 전송됩니다.
HTML Form의 기본 속성
<form> 태그에는 제출 방식을 결정하는 여러 중요한 속성이 있습니다:
1. action 속성
폼 데이터가 전송될 URL을 지정합니다.
<form action="/process-form">
<!-- 폼 내용 -->
</form>
만약 action 속성을 생략하면, 현재 페이지의 URL로 폼이 제출됩니다.
2. method 속성
폼 데이터를 서버로 전송하는 HTTP 메서드를 지정합니다. 주로 사용되는 값은 다음과 같습니다:
get: URL의 쿼리 매개변수로 데이터 전송 (기본값)post: HTTP 요청 본문에 데이터 전송dialog: HTML dialog 요소와 함께 사용 (HTML5.2에서 추가)
<form method="post">
<!-- POST 방식으로 데이터 전송 -->
</form>
3. enctype 속성
폼 데이터의 인코딩 타입을 지정합니다. 주요 값:
application/x-www-form-urlencoded: 기본값. 모든 문자를 인코딩하여 전송multipart/form-data: 파일 업로드 시 필요. 바이너리 데이터 전송 가능text/plain: 공백만 인코딩하고 나머지는 그대로 전송
<form enctype="multipart/form-data">
<!-- 파일 업로드 시 사용 -->
</form>
4. target 속성
폼 제출 후 응답을 표시할 위치를 지정합니다:
_self: 현재 창에 표시 (기본값)_blank: 새 창이나 탭에 표시_parent: 부모 프레임에 표시_top: 전체 창에 표시- 또는 iframe의 name 값
<form target="_blank">
<!-- 응답이 새 탭에 표시됨 -->
</form>
5. novalidate 속성
HTML5 자동 유효성 검사를 비활성화합니다.
<form novalidate>
<!-- 브라우저의 기본 유효성 검사가 수행되지 않음 -->
</form>
6. autocomplete 속성
브라우저의 자동 완성 기능을 활성화/비활성화합니다.
<form autocomplete="off">
<!-- 자동 완성 기능 비활성화 -->
</form>
Submit 버튼의 다양한 구현 방법
폼을 제출하는 방법은 여러 가지가 있습니다:
1. <input type="submit"> 사용
가장 기본적인 제출 버튼입니다.
<input type="submit" value="제출하기">
2. <button type="submit"> 사용
더 다양한 스타일링과 내부 콘텐츠를 포함할 수 있습니다.
<button type="submit">
<img src="icon.png" alt=""> 제출하기
</button>
3. <input type="image"> 사용
이미지를 제출 버튼으로 사용합니다.
<input type="image" src="submit-button.png" alt="제출하기">
4. 자바스크립트로 폼 제출
특별한 경우에는 자바스크립트를 사용하여 폼을 프로그래매틱하게 제출할 수 있습니다.
// form 요소에 접근하여 submit() 메서드 호출
document.getElementById('myForm').submit();
5. Enter 키 동작
대부분의 브라우저에서는 폼 내부의 텍스트 입력 필드에 포커스가 있을 때 Enter 키를 누르면 자동으로 폼이 제출됩니다. 단, 폼 내에 type="submit" 버튼이 하나만 있을 경우에 작동합니다.
Form 데이터 전송 방식
폼 데이터는 method 속성에 따라 다르게 전송됩니다:
GET 방식
- URL의 쿼리 매개변수로 데이터가 전송됩니다.
- 예:
http://example.com/submit?name=홍길동&email=hong@example.com - 특징:
- URL에 데이터가 노출됨
- 데이터 양에 제한이 있음 (~2048자)
- 브라우저 히스토리에 저장됨
- 북마크 가능
- 캐시될 수 있음
- 보안에 취약함
- 주로 검색이나 필터링 같은 데이터 조회에 사용
POST 방식
- HTTP 요청 본문에 데이터가 포함됩니다.
- 특징:
- URL에 데이터가 노출되지 않음
- 데이터 양에 실질적 제한이 없음
- 브라우저 히스토리에 저장되지 않음
- 북마크 불가
- 캐시되지 않음
- GET보다 상대적으로 더 안전함
- 주로 데이터 생성, 수정, 삭제와 같은 작업에 사용
Submit 이벤트와 자바스크립트
폼이 제출될 때 submit 이벤트가 발생합니다. 이 이벤트를 활용하면 폼 제출 전에 추가 작업을 수행할 수 있습니다:
const form = document.getElementById('myForm');
form.addEventListener('submit', function(event) {
// 기본 제출 동작 중지
event.preventDefault();
// 폼 데이터 유효성 검사
if (!validateForm()) {
// 유효성 검사 실패 시 처리
return;
}
// 추가 작업 수행
console.log('폼 제출 전 작업 수행');
// 프로그래매틱하게 폼 제출
this.submit();
});
function validateForm() {
// 유효성 검사 로직
return true; // 유효한 경우
}
FormData 객체 활용
FormData 객체를 사용하면 폼 데이터를 쉽게 수집하고 처리할 수 있습니다:
form.addEventListener('submit', function(event) {
event.preventDefault();
const formData = new FormData(this);
// FormData의 모든 필드와 값 출력
for (const [key, value] of formData.entries()) {
console.log(`${key}: ${value}`);
}
// 데이터 추가 또는 수정
formData.append('timestamp', Date.now());
// 서버로 데이터 전송 (Ajax)
// ...
});
Form 유효성 검사
폼 제출 전에 입력 데이터가 유효한지 검사하는 것은 중요합니다. 유효성 검사는 클라이언트 측과 서버 측 모두에서 수행되어야 합니다.
1. HTML5 내장 유효성 검사
HTML5는 다양한 내장 유효성 검사 기능을 제공합니다:
<form>
<!-- required 속성: 필수 입력 필드 -->
<input type="text" name="username" required>
<!-- type 속성: 특정 형식 강제 -->
<input type="email" name="email">
<!-- pattern 속성: 정규식 패턴 지정 -->
<input type="text" name="zipcode" pattern="[0-9]{5}" title="5자리 우편번호">
<!-- min, max, minlength, maxlength 속성 -->
<input type="number" name="age" min="18" max="100">
<input type="password" name="password" minlength="8">
<button type="submit">제출</button>
</form>
2. 자바스크립트 유효성 검사
더 복잡한 유효성 검사는 자바스크립트로 구현할 수 있습니다:
form.addEventListener('submit', function(event) {
const password = document.getElementById('password').value;
const confirm = document.getElementById('confirm').value;
if (password !== confirm) {
event.preventDefault();
alert('비밀번호가 일치하지 않습니다.');
}
// 추가 유효성 검사...
});
3. 제약 조건 검증 API
HTML5 제약 조건 검증 API를 사용한 유효성 검사:
form.addEventListener('submit', function(event) {
// 모든 필드의 유효성 검사
if (!this.checkValidity()) {
event.preventDefault();
// 사용자 정의 유효성 검사 메시지 표시
const invalidFields = this.querySelectorAll(':invalid');
invalidFields.forEach(field => {
// 필드별 오류 메시지 표시
showError(field, field.validationMessage);
});
}
});
// 특정 필드의 유효성 검사
const emailField = document.getElementById('email');
if (emailField.validity.typeMismatch) {
showError(emailField, '유효한 이메일 주소를 입력하세요');
}
비동기 폼 제출: Ajax와 Fetch API
전통적인 폼 제출은 페이지를 새로고침합니다. 비동기 제출을 사용하면 페이지 새로고침 없이 데이터를 서버로 전송할 수 있습니다.
1. Fetch API 사용
현대적인 웹 애플리케이션에서는 Fetch API를 사용한 비동기 폼 제출이 일반적입니다:
form.addEventListener('submit', async function(event) {
event.preventDefault();
try {
const formData = new FormData(this);
const response = await fetch(this.action, {
method: this.method,
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
throw new Error('서버 응답 오류');
}
const result = await response.json();
console.log('성공:', result);
// 성공 메시지 표시 또는 리디렉션
showSuccess('폼이 성공적으로 제출되었습니다.');
} catch (error) {
console.error('오류 발생:', error);
showError(document.getElementById('form-error'), '제출 중 오류가 발생했습니다.');
}
});
2. JSON 형식으로 데이터 전송
현대적인 API는 종종 JSON 형식의 데이터를 기대합니다:
form.addEventListener('submit', async function(event) {
event.preventDefault();
const formData = new FormData(this);
const formDataObject = Object.fromEntries(formData.entries());
try {
const response = await fetch('/api/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCsrfToken()
},
body: JSON.stringify(formDataObject)
});
const result = await response.json();
// 응답 처리
} catch (error) {
// 오류 처리
}
});
멀티파트 폼 제출과 파일 업로드
파일 업로드를 포함하는 폼은 enctype="multipart/form-data"를 사용해야 합 니다:
<form action="/upload" method="post" enctype="multipart/form-data">
<label for="profile">프로필 사진:</label>
<input type="file" id="profile" name="profile" accept="image/*">
<button type="submit">업로드</button>
</form>
자바스크립트로 파일 업로드 처리
form.addEventListener('submit', async function(event) {
event.preventDefault();
const fileInput = document.getElementById('profile');
if (fileInput.files.length === 0) {
alert('파일을 선택해주세요.');
return;
}
const formData = new FormData(this);
try {
const response = await fetch('/upload', {
method: 'POST',
body: formData
// multipart/form-data를 사용할 때는 Content-Type 헤더를 설정하지 않음
// 브라우저가 자동으로 적절한 경계(boundary)를 포함한 헤더를 설정
});
const result = await response.json();
if (result.success) {
// 업로드 성공 처리
document.getElementById('preview').src = result.fileUrl;
}
} catch (error) {
// 오류 처리
}
});
다중 파일 업로드
<input type="file" id="gallery" name="gallery[]" multiple>
const fileInput = document.getElementById('gallery');
const formData = new FormData();
// 여러 파일 추가
for (const file of fileInput.files) {
formData.append('gallery[]', file);
}
// 또는 더 간단하게
formData.append('gallery[]', fileInput.files[0]);
formData.append('gallery[]', fileInput.files[1]);
// ...
모던 웹 개발에서의 폼 제출 패턴
1. SPA(Single Page Application)에서의 폼 제출
리액트, 뷰, 앵귤러와 같은 프레임워크를 사용하는 SPA에서는 폼 제출이 다르게 처리됩니다:
React 예제:
import { useState } from 'react';
function ContactForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
message: ''
});
const handleChange = (e) => {
setFormData({
...formData,
[e.target.name]: e.target.value
});
};
const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('/api/contact', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
});
const data = await response.json();
// 응답 처리
} catch (error) {
// 오류 처리
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
name="name"
value={formData.name}
onChange={handleChange}
required
/>
{/* 기타 입력 필드 */}
<button type="submit">제출</button>
</form>
);
}
2. 폼 라이브러리 활용
복잡한 폼 관리를 위해 라이브러리를 사용하는 경우도 많습니다:
React Hook Form 예제:
import { useForm } from 'react-hook-form';
function RegisterForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = async (data) => {
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
// 응답 처리
} catch (error) {
// 오류 처리
}
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<input
{...register('username', { required: '사용자명은 필수입니다' })}
/>
{errors.username && <p>{errors.username.message}</p>}
{/* 기타 입력 필드 */}
<button type="submit">가입하기</button>
</form>
);
}
보안 고려사항
폼 제출 시 보안은 매우 중요합니다. 주요 보안 위험과 대응 방법:
1. CSRF(Cross-Site Request Forgery) 방지
CSRF 토큰을 사용하여 폼이 신뢰할 수 있는 소스에서 제출되었는지 확인:
<form method="post" action="/submit">
<input type="hidden" name="csrf_token" value="123456abcdef">
<!-- 나머지 폼 필드 -->
</form>
서버에서는 이 토큰이 유효한지 확인합니다.
2. XSS(Cross-Site Scripting) 방지
사용자 입력을 항상 이스케이프 처리하고, 콘텐츠 보안 정책(CSP)을 구현합니다:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'">
3. 입력 유효성 검사
서버 측에서도 반드시 모든 입력 데이터의 유효성을 검사해야 합니다. 클라이언트 측 유효성 검사만으로는 충분하지 않습니다.
접근성을 고려한 폼 제출
접근성이 좋은 폼은 모든 사용자가 쉽게 사용할 수 있습니다:
1. 레이블 사용
모든 입력 필드에 관련 레이블을 제공:
<label for="name">이름:</label>
<input type="text" id="name" name="name">
2. 오류 처리 및 피드백
오류 메시지를 명확하게 표시하고 스크린 리더 사용자를 고려:
<div class="form-group">
<label for="password">비밀번호:</label>
<input type="password" id="password" aria-describedby="password-error">
<div id="password-error" class="error" role="alert" aria-live="assertive"></div>
</div>
function showError(field, message) {
const errorElement = document.getElementById(`${field.id}-error`);
errorElement.textContent = message;
field.setAttribute('aria-invalid', 'true');
}
3. 제출 버튼 접근성
제출 버튼이 키보드로 접근 가능하고 목적이 명확해야 합니다:
<button type="submit" aria-label="연락처 양식 제출">제출하기</button>
실전 예제
완전한 접근성을 갖춘 연락처 폼
<form id="contactForm" action="/contact" method="post" novalidate>
<div class="form-group">
<label for="name">이름:</label>
<input type="text" id="name" name="name" required aria-describedby="name-error">
<div id="name-error" class="error" role="alert" aria-live="assertive"></div>
</div>
<div class="form-group">
<label for="email">이메일:</label>
<input type="email" id="email" name="email" required aria-describedby="email-error">
<div id="email-error" class="error" role="alert" aria-live="assertive"></div>
</div>
<div class="form-group">
<label for="message">메시지:</label>
<textarea id="message" name="message" required aria-describedby="message-error"></textarea>
<div id="message-error" class="error" role="alert" aria-live="assertive"></div>
</div>
<div class="form-group">
<button type="submit">메시지 보내기</button>
<div id="form-status" role="status" aria-live="polite"></div>
</div>
</form>
document.addEventListener('DOMContentLoaded', function() {
const form = document.getElementById('contactForm');
form.addEventListener('submit', async function(event) {
event.preventDefault();
// 모든 오류 메시지 초기화
clearErrors();
// 유효성 검사
let isValid = true;
const nameField = document.getElementById('name');
if (!nameField.value.trim()) {
showError(nameField, '이름을 입력해주세요.');
isValid = false;
}
const emailField = document.getElementById('email');
if (!emailField.value.trim()) {
showError(emailField, '이메일을 입력해주세요.');
isValid = false;
} else if (!isValidEmail(emailField.value)) {
showError(emailField, '유효한 이메일 주소를 입력해주세요.');
isValid = false;
}
const messageField = document.getElementById('message');
if (!messageField.value.trim()) {
showError(messageField, '메시지를 입력해주세요.');
isValid = false;
}
if (!isValid) {
// 첫 번째 오류 필드로 포커스 이동
form.querySelector('[aria-invalid="true"]').focus();
return;
}
// 제출 중 상태 표시
const statusElement = document.getElementById('form-status');
statusElement.textContent = '메시지를 보내는 중...';
try {
const formData = new FormData(form);
const response = await fetch(form.action, {
method: form.method,
body: formData,
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
const result = await response.json();
if (result.success) {
// 성공 메시지 표시
statusElement.textContent = '메시지가 성공적으로 전송되었습니다!';
form.reset();
} else {
// 서버 오류 메시지 표시
statusElement.textContent = result.message || '메시지 전송 중 오류가 발생했습니다.';
}
} catch (error) {
console.error('오류:', error);
statusElement.textContent = '메시지 전송 중 오류가 발생했습니다.';
}
});
function clearErrors() {
const errorElements = form.querySelectorAll('.error');
errorElements.forEach(element => {
element.textContent = '';
});
const fields = form.querySelectorAll('input, textarea');
fields.forEach(field => {
field.removeAttribute('aria-invalid');
});
}
function showError(field, message) {
const errorElement = document.getElementById(`${field.id}-error`);
errorElement.textContent = message;
field.setAttribute('aria-invalid', 'true');
}
function isValidEmail(email) {
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return regex.test(email);
}
});
결론
HTML 폼 제출은 단순해 보이지만, 사용자 경험과 보안을 위해 많은 고려사항이 필요합니다. 이 글에서 다룬 주요 포인트를 요약하면:
- 기본 원칙 이해하기: 폼의 기본 속성(
action,method,enctype등)과 그 의미를 이해합니다. - 적절한 제출 방식 선택하기: 전통적인 폼 제출과 비동기 방식의 장단점을 고려합니다.
- 유효성 검사 구현하기: 클라이언트 측과 서버 측 모두에서 철저한 유효성 검사를 수행합니다.
서버와 통신 없이 HTML 폼의 데이터를 추출하는 방법은 매우 간단합니다. JavaScript를 사용해서 폼 데이터를 추출할 수 있는 여러 가지 방법을 알려드리겠습니다.
1. FormData 객체 사용하기
FormData 객체는 폼 데이터를 쉽게 추출할 수 있는 가장 현대적인 방법입니다:
// HTML에 있는 form 요소 가져오기
const form = document.getElementById('myForm');
// form 요소로부터 FormData 객체 생성
const formData = new FormData(form);
// 모든 폼 데이터 확인하기
for (const [name, value] of formData.entries()) {
console.log(`${name}: ${value}`);
}
// 특정 필드 값 가져오기
const username = formData.get('username');
console.log('사용자명:', username);
// 같은 이름을 가진 여러 값(예: 체크박스) 가져오기
const interests = formData.getAll('interests');
console.log('관심사:', interests);
2. FormData를 객체로 변환하기
폼 데이터를 일반 JavaScript 객체로 변환하려면:
const form = document.getElementById('myForm');
const formData = new FormData(form);
// FormData를 일반 객체로 변환
const formDataObject = Object.fromEntries(formData);
console.log(formDataObject);
// 또는 더 복잡한 처리를 위한 방법
const formDataObject2 = {};
for (const [name, value] of formData.entries()) {
// 같은 이름의 필드가 여러 개인 경우 배열로 처리
if (formDataObject2[name]) {
if (!Array.isArray(formDataObject2[name])) {
formDataObject2[name] = [formDataObject2[name]];
}
formDataObject2[name].push(value);
} else {
formDataObject2[name] = value;
}
}
console.log(formDataObject2);
3. submit 이벤트 리스너 사용하기
폼이 제출될 때 데이터를 추출하려면:
document.getElementById('myForm').addEventListener('submit', function(e) {
// 폼의 기본 제출 동작 방지
e.preventDefault();
// FormData 생성
const formData = new FormData(this);
// 데이터 처리
const formDataObject = Object.fromEntries(formData);
console.log('폼 데이터:', formDataObject);
// 여기서 데이터를 사용할 수 있음
// 예: 로컬 저장소에 저장
localStorage.setItem('formData', JSON.stringify(formDataObject));
// 사용자에게 피드백 제공
alert('폼 데이터가 저장되었습니다!');
});
4. 개별 입력 요소에서 값 추출하기
DOM 선택자를 사용하여 각 입력 요소의 값을 직접 가져올 수도 있습니다:
function collectFormData() {
const data = {
username: document.getElementById('username').value,
email: document.getElementById('email').value,
password: document.getElementById('password').value
};
// 체크박스 처리
data.newsletter = document.getElementById('newsletter').checked;
// 라디오 버튼 처리
const genderRadios = document.getElementsByName('gender');
for (const radio of genderRadios) {
if (radio.checked) {
data.gender = radio.value;
break;
}
}
// 다중 선택 (셀렉트 박스)
const interestsSelect = document.getElementById('interests');
data.interests = Array.from(interestsSelect.selectedOptions)
.map(option => option.value);
return data;
}
// 버튼 클릭 시 데이터 수집
document.getElementById('collectButton').addEventListener('click', function() {
const formData = collectFormData();
console.log('수집된 데이터:', formData);
});
전체 예제
아래는 서버 통신 없이 폼 데이터를 추출하고 표시하는 전체 예제입니다:
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<title>폼 데이터 추출 예제</title>
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; padding: 20px; }
form { max-width: 500px; margin: 0 auto; }
.form-group { margin-bottom: 15px; }
label { display: block; margin-bottom: 5px; }
input, select, textarea { width: 100%; padding: 8px; box-sizing: border-box; }
button { padding: 10px 15px; background: #4CAF50; color: white; border: none; cursor: pointer; }
#result { margin-top: 20px; padding: 15px; border: 1px solid #ddd; border-radius: 4px; }
pre { background: #f4f4f4; padding: 10px; overflow: auto; }
</style>
</head>
<body>
<h1>폼 데이터 추출 예제</h1>
<form id="userForm">
<div class="form-group">
<label for="username">사용자명:</label>
<input type="text" id="username" name="username">
</div>
<div class="form-group">
<label for="email">이메일:</label>
<input type="email" id="email" name="email">
</div>
<div class="form-group">
<label>성별:</label>
<div>
<input type="radio" id="male" name="gender" value="male">
<label for="male">남성</label>
<input type="radio" id="female" name="gender" value="female">
<label for="female">여성</label>
<input type="radio" id="other" name="gender" value="other">
<label for="other">기타</label>
</div>
</div>
<div class="form-group">
<label>관심사: (여러 개 선택 가능)</label>
<div>
<input type="checkbox" id="tech" name="interests" value="tech">
<label for="tech">기술</label>
<input type="checkbox" id="sports" name="interests" value="sports">
<label for="sports">스포츠</label>
<input type="checkbox" id="movies" name="interests" value="movies">
<label for="movies">영화</label>
<input type="checkbox" id="music" name="interests" value="music">
<label for="music">음악</label>
</div>
</div>
<div class="form-group">
<label for="country">국가:</label>
<select id="country" name="country">
<option value="">선택하세요</option>
<option value="kr">대한민국</option>
<option value="us">미국</option>
<option value="jp">일본</option>
<option value="cn">중국</option>
</select>
</div>
<div class="form-group">
<label for="message">메시지:</label>
<textarea id="message" name="message" rows="4"></textarea>
</div>
<button type="button" id="extractButton">데이터 추출하기</button>
</form>
<div id="result">
<h2>추출된 데이터:</h2>
<pre id="output">여기에 데이터가 표시됩니다.</pre>
</div>
<script>
document.getElementById('extractButton').addEventListener('click', function() {
// 1. FormData 사용
const form = document.getElementById('userForm');
const formData = new FormData(form);
// 2. 객체로 변환
const formDataObject = {};
for (const [name, value] of formData.entries()) {
// 체크박스와 같이 같은 이름이 여러 개인 필드 처리
if (name === 'interests') {
if (!formDataObject[name]) {
formDataObject[name] = [];
}
formDataObject[name].push(value);
} else {
formDataObject[name] = value;
}
}
// 3. 결과 표시
document.getElementById('output').textContent =
JSON.stringify(formDataObject, null, 2);
});
</script>
</body>
</html>
이 예제는 사용자가 폼을 작성한 후 "데이터 추출하기" 버튼을 클릭하면 모든 폼 데이터를 JSON 형식으로 변환하여 페이지에 표시합니다. 서버로 데이터를 전송하지 않고도 클라이언트 측에서 폼 데이터를 완전히 처리할 수 있습니다.
FormData 객체
이번 챕터에선 파일 여부나 추가 필드 여부 등과 상관없이 통용되는 HTML 폼(form) 전송 방법에 대해 알아보겠습니다.
FormData는 폼을 쉽게 보내도록 도와주는 객체입니다. 이름을 보고 유추하셨듯이 FormData 객체는 HTML 폼 데이터를 나타냅니다.
생성자는 다음과 같습니다.
`let` formData `=` `new` `FormData``(``[`form`]``)``;`
HTML에 form 요소가 있는 경우, 위와 같은 코드를 작성하면 해당 폼 요소의 필드 전체가 자동 반영됩니다.
fetch 등의 네트워크 메서드가 FormData 객체를 바디로 받는다는 건 FormData의 특징입니다. 이때 브라우저가 보내는 HTTP 메시지는 인코딩되고 Content-Type 속성은 multipart/form-data로 지정된 후 전송됩니다.
서버 관점에선 FormData를 사용한 방식과 일반 폼 전송 방식에 차이가 없습니다
간단한 폼 전송하기
아주 간단한 폼을 전송한다고 가정해봅시다.
보시다시피 아주 짧은 코드로도 전송 처리가 가능합니다.
``` `` `<`form `` `id```` `=``"`formElem`"` ````>` ``` ````` `` `<`input `` `type```` `=``"`text`"` ``` `name```` `=``"`name`"` ``` `value```` `=``"`Bora`"` ````>` ````` ````` `` `<`input `` `type```` `=``"`text`"` ``` `name```` `=``"`surname`"` ``` `value```` `=``"`Lee`"` ````>` ````` ``` `` `<`input `` `type```` `=``"`submit`"` ````>` ``` ```` `` `</`form ```>` ```` ```` `` `<`script ```>` ```` ```` ``` formElem`.``onsubmit` `=` `async` `(``e``)` `=>` `{` e`.``preventDefault``(``)``;` `let` response `=` `await` `fetch``(``'/article/formdata/post/user'``,` `{` `method``:` `'POST'``,` _`body``:` `new` `FormData``(`formElem`)`_ `}``)``;` `let` result `=` `await` response`.``json``(``)``;` `alert``(`result`.`message`)``;` `}``;` ``` ```` ```` `` `</`script ```>` ````
요청을 받아 처리하는 서버 측 코드는 튜토리얼 범위를 넘어서서 추가하진 않았는데, 서버는 POST 요청을 받아 '저장 성공’이라는 응답을 보내준다고 정도만 알고 계시면 됩니다.
FormData 메서드
FormData에 속하는 필드는 아래와 같은 메서드로 수정할 수 있습니다.
formData.append(name, value)–name과value를 가진 폼 필드를 추가formData.append(name, blob, fileName)–<input type="file">형태의 필드를 추가. 세 번째 인수fileName은 (필드 이름이 아니고) 사용자가 해당 이름을 가진 파일을 폼에 추가한 것처럼 설정해줌formData.delete(name)–name에 해당하는 필드를 삭제formData.get(name)–name에 해당하는 필드의 값을 가져옴formData.has(name)–name에 해당하는 필드가 있으면true를, 그렇지 않으면false를 반환
폼은 이름(name)이 같은 필드 여러 개를 허용하기 때문에 append 메서드를 여러 번 호출해 이름이 같은 필드를 계속 추가해도 문제가 없습니다.
append 메서드 이외에 필드 추가 시 사용할 수 있는 메서드로 set도 있습니다. set이 append 메서드와 다른 점은 set은 name과 동일한 이름을 가진 필드를 모두 제 거하고 새로운 필드 하나를 추가한다는 데 있습니다. 따라서 set 메서드를 쓰면 name을 가진 필드가 단 한 개만 있게끔 보장할 수 있습니다. 이 외에 다른 기능은 append 메서드와 동일합니다.
formData.set(name, value)formData.set(name, blob, fileName)
참고로 폼 데이터 필드에 반복 작업을 할땐 for..of 루프를 사용할 수 있습니다.
`let` formData `=` `new` `FormData``(``)``;` formData`.``append``(``'key1'``,` `'value1'``)``;` formData`.``append``(``'key2'``,` `'value2'``)``;` `// key/value 쌍이 담긴 리스트` `for``(``let` `[`name`,` value`]` `of` formData`)` `{` `alert``(```` `` ` ```` `${`name`}` `` `=` `` `${`value`}` ```` ` `` ````)``;` `// key1 = value1, then key2 = value2` `}`
파일이 있는 폼 전송하기
폼을 전송할 때 HTTP 메시지의 Content-Type 속성은 항상 multipart/form-data이고 메시지는 인코딩되어 전송됩니다. 파일이 있는 폼도 당연히 이 규칙을 따르기 때문에 <input type="file">로 지정한 필드 역시 일반 폼을 전송할 때와 유사하게 전송됩니다.
파일이 있는 폼 예시를 살펴봅시다.
``` `` `<`form `` `id```` `=``"`formElem`"` ````>` ``` ````` `` `<`input `` `type```` `=``"`text`"` ``` `name```` `=``"`firstName`"` ``` `value```` `=``"`Bora`"` ````>` ````` Picture: ````` `` `<`input `` `type```` `=``"`file`"` ``` `name```` `=``"`picture`"` ``` `accept```` `=``"`image/*`"` ````>` ````` ``` `` `<`input `` `type```` `=``"`submit`"` ````>` ``` ```` `` `</`form ```>` ```` ```` `` `<`script ```>` ```` ```` ``` formElem`.``onsubmit` `=` `async` `(``e``)` `=>` `{` e`.``preventDefault``(``)``;` `let` response `=` `await` `fetch``(``'/article/formdata/post/user-avatar'``,` `{` `method``:` `'POST'``,` _`body``:` `new` `FormData``(`formElem`)`_ `}``)``;` `let` result `=` `await` response`.``json``(``)``;` `alert``(`result`.`message`)``;` `}``;` ``` ```` ```` `` `</`script ```>` ````
Blob 데이터가 있는 폼 전송하기
fetch 챕터에서 살펴본 바와 같이 이미지 같은 동적으로 생성된 바이너리 파일은 Blob 객체를 사용해 쉽게 전송할 수 있습니다. 이때 Blob 객체는 fetch 메서드의 body 매개변수에 바로 넘겨줄 수 있죠.
그런데 실제 코딩을 하다 보면 이미지를 별도로 넘겨주는 것보다 폼에 필드를 추가하고 여기에 이미지 ‘이름’ 등의 메타데이터를 같이 실어 넘겨주는 게 좀 더 편리합니다.
서버 입장에서도 원시 바이너리 데이터를 받는 것보다 multipart-encoded 폼을 받는 게 좀 더 적합하죠.
아래는 <canvas>를 사용해 만든 이미지를 FormData를 사용해 폼 형태로 다른 추가 필드와 함께 전송하는 예시입니다.
``````` `` `<`body `` ````` `style```` `=``"```` `margin``:``0` ````"` ``` ``````>` ``````` ``````` `` `<`canvas `` `id```` `=``"`canvasElem`"` ``` `width```` `=``"`100`"` ``` `height```` `=``"`80`"` ``` ````` `style```` `=``"```` `border``:``1``px` solid ````"` ``` ``````>` ``````````` `` `</`canvas ```>` ```` ``````` `` `<`input `` `type```` `=``"`button`"` ``` `value```` `=``"`이미지 전송`"` ``` ````` `onclick```` `=``"```` `submit``(``)` ````"` ``` ``````>` ``````` ```` `` `<`script ```>` ```` ```` ``` canvasElem`.``onmousemove` `=` `function``(``e``)` `{` `let` ctx `=` canvasElem`.``getContext``(``'2d'``)``;` ctx`.``lineTo``(`e`.`clientX`,` e`.`clientY`)``;` ctx`.``stroke``(``)``;` `}``;` `async` `function` `submit``(``)` `{` `let` imageBlob `=` `await` `new` `Promise``(``resolve` `=>` canvasElem`.``toBlob``(`resolve`,` `'image/png'``)``)``;` _`let` formData `=` `new` `FormData``(``)``;` formData`.``append``(``"firstName"``,` `"Bora"``)``;` formData`.``append``(``"image"``,` imageBlob`,` `"image.png"``)``;`_ `let` response `=` `await` `fetch``(``'/article/formdata/post/image-form'``,` `{` `method``:` `'POST'``,` `body``:` formData `}``)``;` `let` result `=` `await` response`.``json``(``)``;` `alert``(`result`.`message`)``;` `}` ``` ```` ```` `` `</`script ```>` ```` ```` `` `</`body ```>` ````
예시에서 이미지 Blob을 추가한 코드를 다시 봅시다.
formData`.``append``(``"image"``,` imageBlob`,` `"image.png"``)``;`
이 코드는 폼에 <input type="file" name="image"> 태그가 있고, 사용자 기기의 파일 시스템에서 파일명이 "image.png"(3번째 인수 참고)인 imageBlob 데이터(2번째 인수 참고)를 추가한 것과 동일한 효과를 줍니다.
요청을 받은 서버는 일반 폼과 동일하게 폼 데이터와 파일을 읽고 처리합니다.
요약
FormData 객체는 fetch 등의 네트워크 메서드를 통해 HTML 폼을 보내는데 사용됩니다.
FormData 객체는 HTML 폼(form)을 직접 넘겨 new FormData(form)으로 만들 수도 있고, HTML 폼 없이 다음과 같은 메서드로 필드를 추가해 만들 수도 있습니다.
formData.append(name, value)formData.append(name, blob, fileName)formData.set(name, value)formData.set(name, blob, fileName)
메서드를 사용할 때 주의할 점 2가지가 있습니다.
set메서드는name이 같은 필드 모두를 지우고append는 그렇지 않습니다. 다른 차이는 없습니다.- 파일을 보낼 땐 세 번째 인수가 필요한데 이 인수는 사용자 파일 시스템에서 지정한 파일명과 동일하게 지 정됩니다.
이외에도 다음과 같은 메서드가 있습니다
formData.delete(name)formData.get(name)formData.has(name)
다룰 내용은 여기까지입니다!