[JS] FormData 주고받기

Yunhye Park·2024년 12월 8일
0

Back To Basic

목록 보기
9/10
post-thumbnail

회원가입이나 로그인, 혹은 기타 사용자가 입력한 데이터를 서버에 전달할 때 form을 다루게 된다.

이때 서버에 폼 데이터를 전달하는 방식으로

  1. HTML form 태그의 action과 method를 활용(일반 폼 전송)
  2. 자바스크립트의 formData 생성자 함수를 활용
  3. form 태그와 자바스크립트 코드 병용
  4. react-hook-form, formik 같은 폼 라이브러리로 핸들링
    ...

방법이야 다양하다.

하지만 근본적인 접근 방식을 알아두면 나머지는 다 거기에서 파생된 개념이기에, 1과 2에 대한 내용을 간단히 정리해보자.

일반 폼 전송

일반 폼 전송은 별도의 자바스크립트 코드 필요 없이 HTML로 폼을 전송할 수 있다. 폼 태그에 있는 action, method 프로퍼티에 네트워크 메서드와 통신 결과를 받아올 경로를 지정해주고, type=submit인 input이나 button을 명시하여 전송된다.

<form action="/submit" method="POST">
    <input type="text" name="username" />
    <button type="submit">제출</button>
</form>

브라우저에서 자동으로 데이터를 인코딩해주어서 별도의 형식 명시는 하지 않아도 된다. 기본적으로 application/x-www-form-urlencoded 형식으로 데이터를 전송한다. 단, POST 요청으로 파일을 전송하려면 enctype="multipart/form-data"도 추가 명시해야 한다.

브라우저는 기본적으로 동기 방식이다. 서버와 통신이 원활히 이루어지면, 그 결과 데이터를 보여주기 위해 페이지 전체를 갱신한다. 그런데 폼 제출할 때마다 페이지 전체가 리로드 되는 웹사이트를 요즘엔 웬만해선 경험하기 어렵다.

only HTML 대신 자바스크립트로 핸들링하면 동적인 처리가 가능해서다.

FormData

폼데이터는 폼 필드와 해당 필드의 값을 key-value 쌍으로 나타낸 자바스크립트 객체이다. 이때 value에는 문자열, 숫자(자동으로 문자열 전환), blob 객체, 파일 등을 추가할 수 있다.

서버로 폼을 전송해주는 button 태그 역할을 네트워크 메서드(fetch)가 이를 대신한다. HTTP 통신 headers에서 별도로 폼 전송 형식을 명시하지 않아도 자동으로 폼 데이터 타입에 맞게 인코딩 된다. 또한, 네트워크 통신은 비동기 처리방식이므로 페이지 전체 리로드가 발생하지 않아서 화면 깜빡임 따위가 없다.

new 연산자를 사용한 생성자 함수이기에 메서드를 통한 데이터 추가, 삭제 등이 간단하다.

	const formData = new FormData();

	formData.append('email', 'example@email.com');
	formData.append('password', 'password123!');
	formData.append('username', 'hey');

일부 메서드

  • append: 객체에 이미 key가 있으면 그 키에 새 값 추가 (key 중복 가능)
  • set: 이미 동일한 key가 있으면 새 값으로 대체 (key 중복 불가)
  • entries: key value 쌍을 순회하는 iterator 반환
  • delete: 필드 삭제

🚨 이때 주의사항. value에 객체나 배열을 '바로' 넣을 수 없다.

  1. JSON 직렬화(객체를 문자열로 변환) 해서 보내거나
const obj = { key: 'value' };
formData.append('objData', JSON.stringify(obj));
  1. append 메서드는 동일한 필드명으로 값 추가가 가능하다는 점을 활용해 배열을 하나씩 순회해 추가할 수 있다.

후자의 방법은 2개 이상의 파일을 핸들링할 때 유용하다. 아래 예시를 보자.

FormData로 여러 파일 전송하기

type이 file인 input으로 파일을 업로드할 수 있는데, 이때 데이터는 어떤 타입으로 담길까? 바로 유사배열 객체(Array-like Object)인 FileList이다.

유사배열 객체
'배열'처럼 인덱스로 접근 가능하고length도 있지만, Array.prototype을 상속받지 않아 배열 메서드 등의 사용이 불가한 객체.
ex) FileList를 콘솔에 찍어보면 이런 모양새다.

FileList { 0: File, 1: File, length: 2 }

React나 Next.js 환경에서 간단하게 파일을 담는 상황을 만들어보면 이해가 좀더 쉽다.

const fileRef = useRef();

const handleFileUpload = () => {
  const formData = new FormData();
  const fileList = fileRef.current.files;

  for (const file of fileList) {
    formData.append('files[]', file); // 배열로
  }
  
...

<input
  type='file'
  name='files'
  ref={fileRef}
  multiple={true}
  onChange={handleFileUpload}
 />

파일을 업로드 하면 폼데이터 객체를 생성하고, ref로 타겟팅한 데이터를 가져와 files[]라는 key name으로 각 값을 순회하며 넣어준다.

🚨 이때 주의사항이 있다. map이나 forEach같은 배열 메서드는 FileList에 사용할 수 없다. 앞서 언급한 대로 FileList는 배열이 아니라 유사배열 객체라서 Array.prototype을 상속 받지 않았기 때문이다.

대신 iterable 객체인 점에 착안해 for문 등 다른 방법을 택하면 된다.

FormData가 콘솔로 안 찍힌다?

이제 서버로 폼 데이터를 전송하기 전, 제대로 값이 들어왔는지 확인해보자.

console.log(formData) // FormData {}

그런데 왜 빈 객체로 보이는 걸까? 브라우저에서의 디버깅은 toString 메서드로 객체를 직렬화해서 이루어지는데, formData는 자바스크립트 내장객체이다. 따라서 브라우저에서 문자열화 해도 객체 내부에는 접근할 수 없다.

그래도 iterable 객체인 건 마찬가지라 FileList에서 그러했듯 for문을, 혹은 formData의 메서드인 entries를 활용할 수 있다.

for (const data of formData) {
	console.log(data);
}

for (const [key, value] of formData.entries() {
	console.log(`${key}:`, value)
}

이렇게 값이 잘 담기는 것도 확인했으니 fetch로 서버에 통신을 보내고, 결과값을 핸들링 해주면 된다.

const fileRef = useRef();

const handleFileUpload = async () => {
  const formData = new FormData();
  const fileList = fileRef.current.files;

  for (const file of fileList) {
    formData.append('files[]', file);
  }

  // API 호출
  try {
    const res = await fetch('/api/upload', {
      method: 'POST',
      body: formData,
    });

    if (res.ok) {
      const result = await res.json();
      // 업로드 성공 핸들링
    } else {
      // 업로드 실패 핸들링
    }
  } catch (error) {
    // 통신 에러 핸들링
  }
};

...

<input
  type='file'
  name='files'
  ref={fileRef}
  multiple={true}
 />

위 예시코드에서는 프론트에서 files[]라는 key name으로 전달했다. 고로 서버에서도 동일한 key name으로 받고, 타입을 배열로 명시해야 한다. 이런 내용은 백엔드와 협의 필수!


참고

mdn - FormData
과거의 내가 현재의 나를 먹여살리는 광경

profile
일단 해보는 편

0개의 댓글

관련 채용 정보