multipart form data upload 완전정복!

bclef25·2020년 2월 12일
10

Multipart form data 란?

multipart/form-data is one of the value of enctype attribute, which is used in form element that have a file upload. multi-part means form data divides into multiple parts and send to server.

구글에 정의를 물었을때 나오는 답이다.
multipart/form-data는 파일 업로드가 있는 양식요소에 사용되는 enctype 속성의 값중 하나이고, multipart는 폼데이터가 여러 부분으로 나뉘어 서버로 전송되는 것을 의미합니다.

실제로 적용을 어떻게 하는지 봅시다.

<form method"post" encType="multipart/form-data">
	<input type="file" />
</form>

기본적으로 HTML 파일 내부에 form tag 에 encType="multipart/form-data"라는걸 지정해줘야 합니다. 이 폼이 제출될 때 이 형식이 어떤것인지를 알려주는 곳이죠.

이제 input에 이벤트 핸들러가 필요합니다.
파일을 선택할 때 그 파일을 받아서 내부적으로 처리해줘야 하기 때문이죠.

const handleFile = (e)=>{
	const file = e.target.files[0]
}

e.target.file은 배열이 아닙니다. 그런데 왜 [0]이렇게 되어있을까요.

fileList:{
 0:{name:미디어파일1 , ....},
 1:{name:미디어파일2 , ....},
 length:2,
 item:{어쩌구저쩌구}
}

이런 형태로 들어있습니다.
인덱스로 접근한게 아니라 브라켓 노테이션으로 접근한 것이죠.
그래서 for문이나 map이 돌아가지 않습니다. 이것 때문에 고생을 많이 했죠..
제가 아는 지식 내에서는 for in을 사용하시길 권장합니다.
length가 객체 안에 number로 있기 때문에 for문을 통해 미디어 파일의 개수 만큼 무언가를 할수있습니다.

자 그럼 우선 선택한 파일을 저장해야겠죠
여러 파일을 한번에 선택해도 받을 수 있게 되는 방법을 선택하였습니다.
요즘시대에 파일을 한번에 하나씩만 올릴 수 있다는건 말이안되죠..
그 말이 안되는 로직을 짰다가 갈아 엎었습니다........신입의 꽃은 역시 야근인가 봅니다.

const file = e.target.files
for(let i = 0 ; i < e.target.files.length: i++){
	const reader = new FileReader();
    reader.readAsDataURL(file[i]);
}

우선 들어오는 파일의 개수만큼 fileReader로 읽어들입니다.
읽어들인 각각의 파일을 readAsDataURL로 url 처리 해줍니다.

*readAsDataURL : readAsDataURL 메소드는 지정된 Blob 또는 File의 컨텐츠를 읽는 데 사용됩니다. 읽기 작업이 완료되면 readyState가 DONE이되고로드 엔드가 트리거됩니다. 이때 결과 속성은 데이터를 base64로 인코딩 된 문자열로 파일의 데이터를 나타내는 data : URL로 포함합니다.

이제 우리가 원하는 형식으로 파일을 가공한 것이죠.

  const fileTypes = [
      "image/jpeg",
      "image/pjpeg",
      "image/png",
      "image/svg",
      "video/mp4",
      "video/mov",
      "video/ogg",
      "video/avi",
      "video/quicktime",
      "image/svg+xml"
    ];
     for (let j = 0; j < fileTypes.length; j++) {
        if (file[i].type === fileTypes[j]) {
          reader.onloadend = () => {
            let concatName: string[] = [];
            let concatFile: any[] = [];
            concatName = concatName.concat(file[i].name);
            concatFile = concatFile.concat({
              category: file[i].type,
              multipart_form_data: file[i]
            });
            this.setState(
              {
                fileName: this.state.fileName.concat(concatName),
                media: this.state.media.concat(concatFile)
              },
              () => {
                console.log(this.state);
              }
            );
          };
        }
      }

for문을 통해 const fileTypes 에 해당하는 것들만 state에 저장하려고 합니다.
파일 네임을 preview하기 위해 fileName도 따로 배열로 저장했습니다.
이렇게 하면 스테이트에 제 파일들을 올려 놓을 수 있게 되죠.

파일을 전송하는 방법에 대해 알아보겠습니다.

const handleSubmit =()=>{

}

const
form data 에 내가 등록한 미디어 파일과 json형식에 파일을 같이 append하여 한번에 formData로 보내려고 합니다.

  const data = {
       		data1:{
            	innerData1:"innerData1",
                innerData2:"innerData2"
            }
            data2:{
            	innerData1: this.state.value1,
                innerData2: this.state.value2
            }
        };

data에 data2의 안쪽의 키값들 처럼 원하는 데이터를 연결해 주시면 되겠습니다.
이제 data라는 객체와 아까 스테이트에 올려놓은 미디어파일을 함께 append 해보겠습니다.

const jsonData = JSON.stringify(data);

우선 data를 JSON.stringify 해줍니다. json형태로 보낼거니까요.

const formData = new FormData();

폼데이터를 생성합니다.

 formData.append("data", jsonData);

jsonData를 "data"라는 키의 값으로 formData에 append 합니다.

for (let i = 0; i < this.state.media.length; i++) {
            formDataF.append(
              "media",
              this.state.media[i].multipart_form_data
            );
          }

for문으로 우리가 가진 미디어의 length 만큼 순회하며 "media"라는 키에 미디어파일을 값으로 append 합니다. 위의 concatFile을 this.state.media에 저장하고 그중에 파일이 담긴
this.state.midea[i].multipart_form_data 에 저장된 파일을 append 하는 것이죠.

이후 fetch를 보낼 때 body에 우리가 append한 내용이 들어있는 formData를 넣어주는 것으로 끝입니다.

그리고 주의 할 점으로는 header에 type을 명시하면 안된다는 점입니다.왜 이런지는 아직 모르겠습니다만.. 이미 form tag에 명시했었죠. 그것으로 대체 되는것 같습니다.

profile
프론트 개발자

2개의 댓글

comment-user-thumbnail
2020년 2월 12일

좋은 글이네요! :)

1개의 답글