게시글

강좌14 - HTML&DOM - 2년 전 등록

FormData

AJAX 폼 전송 준비
조회수:
0

안녕하세요. 이번 시간에는 AJAX로 폼 전송을 가능케해주는 FormData 객체에 대해 알아보겠습니다.

보통은 AJAX로 폼(form 태그) 전송을 할 일이 거의 없습니다. 주로 JSON 구조로 키-값 데이터를 전송하니까요. 하지만 폼 전송이 필요한 경우가 있습니다. 바로 이미지를 AJAX로 업로드할 경우입니다. 물론 이미지는 base64나, buffer, 이진 데이터 형식으로 서버로 전송해도 됩니다. 하지만 가장 자연스러운 것은 폼을 통해서 업로드하는 것이죠. input[type=file]을 사용해서요.

그런데 폼과 AJAX를 같이 쓴다는 것은 좀 이상하죠? 보통 폼 하면 제출 버튼을 누르면 action 속성에 지정한 페이지로 이동하면서 데이터를 전송하니까요. AJAX는 반대로 제출 버튼을 누르면 기본 폼 동작은 e.preventDefault()로 멈추고, 페이지 전환 없이 데이터를 전송합니다.

페이지 전환 없이 폼 데이터를 제출하고 싶을 때 바로 FormData 객체를 사용합니다. AJAX 전송법은 다음 시간에 알아보고, 이번 시간에는 이 객체에 대해서 자세히 살펴보겠습니다.

FormData 객체는 window.FormData에 위치합니다.

var formData = new FormData();
formData.append('name', 'zerocho');
formData.append('item', 'orange');
formData.append('item', 'melon');

자, new FormData()로 새로운 객체를 생성해주시고요. append 메소드로 키-값 형식으로 하나씩 추가해주시면 되겠습니다. 같은 키를 가진 값을 여러 개 넣을 수도 있습니다. 덮어씌워지지 않고 추가됩니다. 참고로 값은 문자열로 자동 변환됩니다. 숫자를 넣어도 문자열이 되고, 배열을 넣어도 콤마로 구분한 문자열이 됩니다. 객체는 넣으면 무시됩니다. 이 점을 유의하세요!

기존 폼이 있는 경우는

var formData = new FormData(document.getElementById('폼 아이디'));

이렇게 선택하면 알아서 폼의 내용들의 formData 객체 안에 들어갑니다. 편리하죠?

append로 넣을 수만 있는 게 아니라 내용물을 확인할 수도 있습니다. 위의 코드와 이어집니다.

formData.has('item'); // true
formData.has('money'); // false
formData.get('item'); // orange
formData.getAll('item'); // ['orange', 'melon']

has 메소드로는 해당하는 키가 존재하는 지 확인할 수 있고요. get 메소드로 직접 가져올 수 있습니다. 유의할 점은 get은 처음 저장한 값 하나만 불러옵니다. 위에서 item 키에 값을 orange와 melon 두 개를 줬습니다. 그런데 get만 하면 orange밖에 뜨지 않습니다. 이 때 getAll 메소드가 있습니다. 해당 키에 매칭되는 값들을 전부 배열로 반환합니다.

var keys = formData.keys();
keys.next(); // { done: false, value: 'name' }
keys.next(); // { done: false, value: 'item' }
keys.next(); // { done: false, value: 'item' }
keys.next(); // { done: true, value: undefined }
var values = formData.values();
values.next(); // { done: false, value: 'zerocho' }
values.next(); // { done: false, value: 'orange' }
values.next(); // { done: false, value: 'melon' }
values.next(); // { done: true, value: undefined }
var entries = formData.entries();
entries.next(); // { done: false, value: ['name', 'zerocho'] }
entries.next(); // { done: false, value: ['item', 'orange'] }
entries.next(); // { done: false, value: ['item', 'melon'] }
entries.next(); // { done: true, value: undefined }

위의 코드는 반복자(이터레이터)를 사용한 코드입니다. 이터레이터 강좌는 여기를 참조하세요. 이터레이터는 상황에 따라 매우 유용할 수 있습니다. 물론 IE에서 안 되는 단점이 있지만요. FormData의 키나 값, 또는 키와 값 모두를 쉽게 보여줍니다. 반복문에 사용하면 좋습니다.

formData.append('test', ['hi', 'zero']);
formData.get('test'); // hi, zero
formData.delete('test');
formData.get('test'); // null
formData.set('item', 'apple');
formData.getAll('item'); // ['apple']

formData에 이제 값을 지워봅시다. delete 메소드를 사용하면 됩니다. append와 비슷한 set 메소드도 있습니다. set과 append의 차이점은 set도 추가를 해주기는 하지만, 기존 키가 있으면 그 값을 모두 덮어씌워버립니다. 위의 item에 대한 값이 orange와 melon이었는데 apple을 set하는 순간 item 값이 apple 하나로 덮어씌워졌습니다.

이상으로 FormData의 API에 대해 알아봤습니다. FormData에 이미지를 담고 싶으면

formData.append('img', document.getElementById('파일 인풋').files[0])

위와 같이 넣어주고 서버로 formData를 보내버리면 됩니다. 파일이 여러 개면 반복문으로 append를 하면 되고요. 주의할 점은 여러 개를 append할 때 항상 키값(위에서는 img)은 같아야 여러 파일이 같은 키로 업로드 됩니다.

네, 이제 폼데이터는 마스터 하셨고요. 이 값을 서버로 보내기만 하면 됩니다. AJAX로 보내려면 XMLHttpRequest에 대해서 알아야겠죠? 다음 시간에는 XMLHttpRequest에 대해 알아봅시다!

투표로 게시글에 관해 피드백을 해주시면 많은 도움이 됩니다. 오류가 있다면 어떤 부분에 오류가 있는지도 알려주세요! 잘못된 정보가 퍼져나가지 않도록 도와주세요.
Copyright © 2016- 무단 전재 및 재배포 금지

댓글

9개의 댓글이 있습니다.
4달 전
multer로 넘어가기 전에 /post/img로 전달되는 과정에서 오류가 나는 것 같아요
4달 전
서버쪽 콘솔 보시면 /post/img에만 빨간색으로 500 떠있는거죠?
4달 전
네 ㅠㅠ
4달 전
아 network response 쪽 보니까 이렇게 뜹니다!

MulterError: Unexpected field
at wrappedFileFilter (\node_modules\multer\index.js:40:19)
at Busboy.<anonymous> (\node_modules\multer\lib\make-middleware.js:114:7)
at Busboy.emit (events.js:189:13)
at Busboy.emit (\node_modules\busboy\lib\main.js:38:33)
at PartStream.<anonymous> (\node_modules\busboy\lib\types\multipart.js:213:13)
at PartStream.emit (events.js:189:13)
at HeaderParser.<anonymous> (\node_modules\dicer\lib\Dicer.js:51:16)
at HeaderParser.emit (events.js:189:13)
at HeaderParser._finish (\node_modules\dicer\lib\HeaderParser.js:68:8)
at SBMH.<anonymous> (\node_modules\dicer\lib\HeaderParser.js:40:12)
4달 전
이건 formData에서 append할 때 img를 키로 주셨는데 멀터에서는 키가 img가 아닌것 같네요.
4달 전
아 정말 감사합니다ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ 몇 일 동안 해결 안되던게 드디어 해결됐어요ㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠㅠ
4달 전
서버쪽 오류는 어떻게 출력해야 할까요..?
4달 전
서버쪽 multer를 쓰는 부분에서 자체적으로 에러가 표시되지 않나요?
4달 전
axios({
method: 'POST',
url: '/post/img',
data: { fd },
headers: { 'Content-Type': 'multipart/form-data' }
}).then(res => console.log(res))
.catch(err => console.log(err.response));
});

위와 같이 수정했는데 request failed with status code 500 에러가 뜹니다.
data 쪽에 오류가 나는 것 같습니다. data를 제외하면 에러가 안나요.
또, fd에 값이 안들어갑니다. err.response 찍어보니까 아래같이 뜨네용 ㅠㅠ

config:
data: "{"fd":{}}"
headers: {Accept: "application/json, text/plain, */*", Content-Type: "multipart/form-data"}
method: "post"
url: "/post/img"
status: 500
statusText: "Internal Server Error"
4달 전
서버쪽에서 에러를 찍어보세요. 서버쪽 에러라서 클라이언트에서 에러 찍는 것은 아무 의미가 없습니다.
4달 전
console.log(this.files) 시

File
lastModified: 1556559632967
lastModifiedDate: Tue Apr 30 2019 02:40:32 GMT+0900 (한국 표준시) {}
name: "f88acab7ffd127b4465659500aa0538f.gif"
size: 849199
type: "image/gif"
webkitRelativePath: ""
__proto__: File

console.log(fd) 시

FormData
__proto__: FormData
4달 전
참고로 formData는 원래 빈 객체로 보이는 게 맞습니다. 내부 속성이 없으니까요. append는 폼으로 추가하는 것이지 내부 속성으로는 아닙니다.
4달 전
멀터쪽에서 어떤 에러가 나나요?
4달 전
안녕하세요~ nodebird 강의 보고 있습니다.
multer로 이미지 업로드하는 과정에서 오류가 발생해서 질문드립니다.

if (document.querySelector('#upload')) {
document.querySelector('#upload').addEventListener('change', function() {
const fr = new FileReader();
const fd = new FormData();

fr.readAsDataURL(this.files[0]);
fd.append('img', this.files[0]);

fr.onload = () => {
document.querySelector('.nav-form-preview').style.display = 'flex';
document.querySelector('.nav-form-preview img').src = fr.result;
}

document.querySelector('.nav-form').addEventListener('submit', () => {
axios.post('/post/img', { fd });
});
});
}

1. filereader는 썸네일 만드는데 사용했구요, fr에는 값이 제대로 들어가는데
formdata가 빈 객체로 출력됩니다.

2. 강의에는 onchange 부분에 화살표 함수를 사용하셨던데 제가 했을 때는
this 값이 window라 일반 함수로 작성하였습니다. 왜 다를까요?

3. xhr 말고 axios로 작성해보았는데 저렇게 써도 되는건지 모르겠습니다.

질문이 너무 많네요ㅜㅜ
4달 전
this.files 콘솔 로그 시에도 제대로 뜨나요? 저는 보통 event.target.files를 습니다. this가 필요하지 않은 상황에는 화살표함수로 보통 합니다. axios로 하셔도 됩니다. axios로 할 때 옵션으로 content-type을 알려주셔야할 것 같습니다. form-data컨텐츠라는 것을요.
일 년 전
var n='kim';
var formData = new FormData();
formData.append("name",n);
이런식으로 변수 n값을 name키 value 값으로 지정해주고 싶은데 어떻게 해야하나요?
일 년 전
지금처럼 formData.append(키값, 데이터값) 하시면 됩니다.
일 년 전
pug에서 10개의 input#p10(type='radio', name='starinput', value='10') 이런 radio버튼이 있는데, 선택된 radio값을 ajax로 제출하고 싶으면 formData에 어떻게 넣어야 하나요??
일 년 전
formData.append('아무거나', '라디오값') 여기서 라디오값은 input 10개에서 반복문을 돌아 checked된 것의 value를 가져오셔야 합니다.
일 년 전
서버쪽에서 받을때 req.body.아무거나 에 해당 값이 들어있는거 아닌가요?? 저는왜 req.body가 undefined가 나오는지 모르겠습니다ㅠㅠㅠ
일 년 전
multer같은 거 설치하셨나요? multipart/form-data는 아무나 받을 수 있는게 아닙니다
일 년 전
아뇨 이미지를 업로드 하는게 아니라 라디오값은 숫자를 넣었습니다 숫자만 넘기면 돼서요 이미지만 가능한건가요??
일 년 전
아뇨 formdata인 경우 이미지든 아니든 multer 무조건 쓰셔야 합니다.
일 년 전
아하! 감사합니다 블로그 너무 좋아요 답변너무감사합니다^^(애드블록 껐습니다ㅎ)
일 년 전
감사합니다. 이런 방식으로 해서 비동기식으로 이미지 파일을 업로드 하는 것은 성공 하였습니다. 그런데 이상하게도 다른 것은 초기화가 되지 않으나 checkbox들은 모두 해제가 되어 난감한 상황이 되었습니다. 폼을 분리해도 상황은 같아 졌습니다. 이런 경우 어떻게 하나요?
일 년 전
폼을 전송하면 체크박스가 해제된다는 말씀이신가요? ajax로 전송하는데도요??
일 년 전
네 다른 값들 input text 값들은 그대로 인데 특이하게 checkbox만 다 풀리네요. 이러면 별도의 페이지에서 새로 올려야 하는데. 해결 방법을 찾다가 잘 정리하신 글을 보고 문의 드리게 되었습니다.
2년 전
항상 유익한 정보 감사합니다. 오늘도 하나 배우게 가네요.