개발 관련/Vue

[vue.js] 데이터를 묶어서 전송해보자 FormData(feat. 단일, 다중 파일 전송)

Hago하고 2021. 6. 3. 20:02
반응형

데이터를 전송하는 일은 많다.

컴포넌트 단에서 바인딩할수도있고, 이벤트로 올리고 프롭스로 받을 수도 있다.

하지만 컴포넌트 간에서 바인딩하고 프롭스를 쓰는 것은 vuex를 쓰면 아주아주 손쉽게 사용할 수 있게되고,

내가 겪은 부분은 API를 통해 데이터를 보내는 부분이었다.

 

<form @submit.prevent="submitForm" class="form" enctype="multipart/form-data">
            <div>
                <span><b>Title</b></span>
                <input type="text" placeholder="Title" v-model="title">
            </div>
            <div>
                <span><b>Authors</b></span>
                <input type="text" placeholder="Authors" v-model="author">
                <button type="button" @click="addAuthor" class="addBtn">Add</button>
            </div>
            <div class="authorsPreview" v-for="item in authors" :key="item.index">
                <span class="createdAuthor">{{ item.author }}</span>
            </div>
            <div>
                <span><b>Publisher</b></span>
                <input type="text" placeholder="Publisher" v-model="publisher">
            </div>
            <div>
                <label for="inputGroupFile01" class="file"><b>Cover</b></label>
                <input type="text" id="coverRoute" placeholder="Cover" disabled />
                <button type="button" @click="findCover" class="addBtn">Add</button>
                <input type="file" id="inputGroupFile01" ref="cover" style="display:none" @change="coverFileSelect">
            </div>
            <div>
                <label for="inputGroupFile02"><b>Pages</b></label>
                <input type="text" id="PagesRoute" placeholder="Pages" disabled />
                <button type="button" @click="findPages" class="addBtn">Add</button>
                <input type="file" id="inputGroupFile02" ref="pages" style="display:none" @change="pagesFileSelect" multiple>
            </div>
            <button type="submit">Submit</button>
            <button type="button" class="clearBtn" @click="clearAll">Clear</button>
        </form>

대충 이렇게 form 태그 안에 인풋 태그를 덕지덕지 바르고 css로 양념을 쳐놨다.

 

오늘 볼 부분은 file부분이니 file 태그를 설명하자면,

우선 text타입의 input을 넣어 disabled로 설정 해놓고, 실제로 file버튼은 보이지 않게 css를 변경해놓았다.

file은 오직 button태그를 통해서만 실행되고, 해당 값이 불러와지면, text타입의 input에 경로를 표시하도록 했다.

(왜 그렇게했냐면... 파일 태그 너무 디자인이 구리기 때문..)

 

버튼을 누르면 파일 선택이 눌리는 것은 아래와 같이 document로 해당 태그를 클릭해주도록 했다.

findCover() {
            document.getElementById("inputGroupFile01").click();
},
findPages() {
            document.getElementById("inputGroupFile02").click();     
},

너무 css적인 측면으로 온 것 같으니 본론으로 돌아가야겠다.

 


내가 보내야 될 태그는 5가지다.

title(책 제목), authors(저자, 2명이상도 됨), publisher(출판사), cover(책표지-파일), pages(책내용-다중파일)

처음에는 이 데이터들을 새로운 객체로 묶어서 보내면 되지 않을까 했는데, 절대 안됬다.

텍스트는 보내졌는데, 파일이 첨부가 안되었다.

 

파일 첨부를 하기 위해 찾던 도중, formData에 대해 알게되었다.

formData객체를 생성해주고 그 곳에 append로 옮길 데이터를 지정하고 해당 값을 전송 서버에 보내면 되는 것이었다.

 

const bookData = new FormData();
bookData.append("title", this.title);
bookData.append("authors", this.authors.map(authors => {return authors.author}));
bookData.append("publisher", this.publisher);
bookData.append("cover", this.cover);
for(let i=0; i<this.pages.length; i++) {
	bookData.append("pages["+i+"]", this.pages[i]);
}

여기서 map을 쓴 이유는 authors맵에 프리뷰용으로 설정해놓은 index가 있었기 때문에,

index는 빼고 저자 이름만 넘기고 싶었기 때문에 저런 방식을 썼다.

 

자, 이제 파일 전송에 대해 이야기를 하자면

유일한 파일 목록인 cover와 pages는 각각 단일, 다중 파일이다.

단일파일인 cover의 경우 bookData.append("cover", this.cover)를 통해 전송이 가능하다.

하지만 다중 파일의 경우, 위의 방법대로 하니 데이터는 가긴 하지만 서버 쪽에서 받아지지 않았다.

 

뭐가 문젠가 고민하고 있던 찰나, 팀장님께서 알려주신 rfc 문서를 보면 다음과 같이 써져있다.

multiple files MUST be sent by supplying each file in a separate part but all with the same "name" parameter.

라고 친절하게 대문자와 쌍따옴표로 강조를 하고 있었다.

 

이 말뜻은 무엇일까.

다중 파일은 각각의 파일을 보내야하는데 꼭 같은 이름을 써야한다고 나와있다.

 

다시 내 코드로 돌아와보자

for(let i=0; i<this.pages.length; i++) {
	bookData.append("pages["+i+"]", this.pages[i]);
}

위와같이하면 page0 : this.pages[0], page1:this.pages[1], page2:this.pages[2]와 같이 각각의 이름으로 파일이 날아가게 된다.

여러 파일이여도 여기서는 이를 pages라는 이름으로 통일해주면 된다.

for(let i=0; i<this.pages.length; i++) {
	bookData.append("pages", this.pages[i]);
}

이렇게하면 여러 파일들이 전송이 되는 것을 확인할 수 있다.

아, 추가로 이러한 폼형태의 데이터를 보내려면 통신할때 헤더에 multiple/form-data가 추가되어있어야하는데,

이는 이런 식으로 구현했다.

function sendBookInfo(bookData) {
    return axios.post('서버 주소', bookData, {
        header: {
            'Context-Type': 'multipart/form-data',
        }
    });
}

컨텍스트 타입을 multipart/form-data로 설정해두면 된다.

 

이상 단일, 다중 파일 올리기도 끝!

반응형