• ✏️ formData와 사용법, 비제어 컴포넌트 활용

    2024. 8. 28.

    by. 서카츄

     

    제어 컴포넌트와 비제어 컴포넌트에 대해서 공부를 했다.

    이번에 투두리스트를 비제어 컴포넌트로 다시 만들어 보았다.

    코드 정리가 필요할 것 같아서 작성해보고, `FormData`는 무엇인지 정리해보았다.

     

     

     

    ✨ 제어 컴포넌트와 비제어 컴포넌트 정리요약은 요기

     

    ✏️ 제어 컴포넌트와 비제어 컴포넌트

    제어 컴포넌트와 비제어 컴포넌트`제어 컴포넌트`는 React에 의해서 값이 제어 되는 것, `비제어 컴포넌트`는 React에 의해서 값이 제어되지 않는 컴포넌트 이다.   제어 컴포넌트제어 컴포넌트

    seokachu.tistory.com

     

     

     

    먼저 FormData란 무엇인가?

     

    자바스크립트에서 `FormData`를 다루는 객체이다.

    `이미지`, `파일`등을 서버로 보내줄 때 FormData 객체가 많이 사용된다.

     

     

    multipart 와 form-data

    `fetch` 등의 네트워크 메서드가 `FormData` 객체를 바디로 받는다는 건 `FormData`의 특징이다.

    이때 브라우저가 보내는 `HTTP` 메시지는 인코딩되고, `Content-Type` 속성은 `multipart/form-data`로 지정된 후 전송된다.
    서버 관점에선 `FormData`를 사용한 방식과 일반 폼 전송 방식에 차이가 없다.

     

    즉, `Content-Type` 속성이 `multipart/form-data`로 지정되고, 정해진 형식에 따라 메시지를 인코딩하여 전송한다. 
    파일이나 이미지(jpg,png) 등이 파일 자체가 전송되는 것이 아니고 인코딩된 데이터로 전송된다. 

     

     

    `FormData`는 `HTML`단이 아닌 `자바스크립`트 단에서 폼 데이터를 다루는 객체라고 보면 된다.

    let formData = new FormData(); //new FromData()로 새로운 객체 생성
    formData.append('title','foo'); //<input name="title" value="foo"> 와 같음
    formData.append('content','bar'); //<input name="content" value="bar"> 와 같음

     

    FormData 객체에 append 메서드로 `key`와 `value` 값을 추가해주면 input 태그에 값을 입력해주는 것과 같은 내용을 갖게 된다.

     

    `formData.append(name, value)` 함수를 이용해 데이터를 넣을시 value는 문자열로 입력된다.

    문자열 이외의 데이터 타입을 넣으면 무시되고 문자열로 자동 변환된다.

    객체나 배열인 데이터는 넣을 수 없다.

     

    굳이 HTML단에서 안보내고 자바스크립트에서 보낼 필요가 있나?

    자바스크립트 단에서 `form` 전송 동작이

    이미지, 파일을 페이지 전환 없이 비동기로 제출 하고 싶을 때, 

    자바스크립트로 타이트하게 데이터를 관리하고 싶을 때 `formData` 객체를 이용한다고 생각하면 된다.

     

     

     

    ✨ FormData 메소드들

    formData.append(name, value)
    // - form의 name 과 value 를 필드의 추가
    // - input의 name 속성과 value 입력값 역할을 한다고 생각 하면 된다.
    
    formData.append(name, blob, fileName)
    // - input 의 type 이 'file' 인 경우에 사용
    // - fileName은 file의 이름의 해당
    
    formData.delete(name)
    // - 주어진 name 으로 필드를 제거
    
    formData.get(name)
    // - 주어진 name 의 해당 하는 필드 value를 반환
    
    formData.getAll(name)
    // - append 함수로 추가시 name이 중복 가능
    // - 따라서 주어진 name 의 해당 하는 필드의 모든 value를 반환
    
    formData.has(name)
    // - 주어진 name 의 해당하는 필드가 있을 경우 true, 없으면 false를 반환
    
    formData.set(name, value)
    formData.set(name, blob, fileName)
    // - set 함수는 append 함수 처럼 필드를 추가
    // - append와 비슷한 set 메소드는 set도 추가를 해주기는 하지만, 기존 key가 있으면 그 key값을 모두 덮어씌워버린다

     

     

     

     

    ✨ formData 메소드 활용

    let formData = new FormData(); // 새로운 폼 객체 생성
    formData.append('item', 'hi');
    
    // key가 존재하는 지 확인. 값이 boolean type으로 반환됩니다.
    formData.has('item'); // true.
    formData.has('money'); // false
    
    // 값의 첫 번째 값이 반환됩니다.
    formData.get('item'); // hi. 
    
    // 중복된 key값을 모두 가져올 때 배열 형식으로 가져옵니다.
    formData.append('item', 'hello'); // 똑같은 item 키에 값을 또 추가
    formData.getAll('item'); // ['hi','hello']

     

    // 중복 된 값을 넣을때는 배열로 한꺼번에 append할 수 있습니다.
    formData.append( 'test', ['hi','hyemin'] );
    formData.get('test'); // hi,hyemin
    
    // 삭제
    formData.delete('test');
    formData.get('test'); // null값이 들어갑니다.
    
    // item값을 수정합니다.
    formData.set('item','test2');
    formData.getAll('item); // ['test2']

     

     

     

     

    `FormData`경우 특수한 객체 형태라서, `console.log`나 `FormData` 속성에서 확인이 불가능하다. 

    확인이 필요한 경우에는 `for문`으로 `FormData`에 형태를 확인할 수 있다. 

    let formData = new FormData();
    formData.append('key1', 'value1');
    formData.append('key2', 'value2');
     
    // 폼 객체 key 값을 순회.
    let keys = formData.keys();
    for (const pair of keys) {
        console.log(pair); 
    }
     
    // 폼 객체 values 값을 순회.
    let values = formData.values();
    for (const pair of values) {
        console.log(pair); 
    }
     
    // 폼 객체 key 와 value 값을 순회.
    let entries = formData.entries();
    for (const pair of entries) {
        console.log(pair[0]+ ', ' + pair[1]); 
    }

     

     

     


     

     

    formData에 이미지 담기

    이미지와 같은 멀티미디어 파일을 폼 데이터에 담고 싶다면, formData 객체를 생성하고,

    이미지 파일이 담긴 input 태그를 querySelector 로 받아와서 files[0] 를  append로 더해주면 된다.

    <body>
        <input type="file" id="fileInput">
        <button type="submit" id="sendButton">전송</button>
     
        <script>
            const fileInput = document.querySelector("#fileInput");
            const sendButton = document.querySelector("#sendButton");
     
            sendButton.addEventListener("click",function(){
     
                var formData = new FormData();
                // form Data 객체 생성
                formData.append("attachedImage", fileInput.files[0]);
                // 파일 인풋에 들어간 파일들은 files 라는 리스트로 저장된다.
                // input에 multiple을 선언해 여러개의 파일을 선택한 경우가 아니라면 files[0] 으로 input에 추가한 파일 객체를 찾을 수 있다.
                
            });
     
        </script>
    </body>

     

     

     


     

     

    formData 값을 객체로 받기

    // 폼데이터에 넣을 key-value 값들을 객체로 관리
    const obj = {
        first: 'Akash',
        middle: 'Rishi',
        last: 'Mittal',
    }
        
    const formData = new FormData();
    
    Object.entries(obj).forEach(item => formData.append(item[0], item[1]));
    // 성능을 따진다면, 고차 함수 대신 for문을 이용해도 된다.
    // for(let key in obj) {
    //   formData.append(key, obj[key])
    // }
    
    // 폼데이터 값 출력
    let entries = formData.entries();
    for (const pair of entries) {
        console.log(pair[0]+ ', ' + pair[1]); 
    }

     

     

    객체의 폼 데이터를 자바스크립트 객체로 환원이 가능하다.

    const obj2 = {};
    formData.forEach((value, key) => obj2[key] = value);
    
    console.log(obj2); // {first: 'Akash', middle: 'Rishi', last: 'Mittal'}

     

     

     


     

     

    fetch로 폼데이터 전송하기

    자바스크립트에서 서버로 form data를 보내려면 body 속성 부분을

    일반적인 json이나 객체 타입 형태가 아닌 form data 타입 형식에 맞춰서 보내야 한다.

     

    var formData = new FormData();
    formData.append('key1', 'value1');
    formData.append('key2', 'value2');
    
    fetch('https://httpbin.org/post', {
        method: 'POST',
        cache: 'no-cache',
        body: formData // body 부분에 폼데이터 변수를 할당
    })
    .then((response) => response.json())
    .then((data) => {
        console.log(data);
    });

     

    formData를 보낼때, header 부분은 브라우저가 자동으로 설정해주기 때문에 Content-Type을 따로 지정할 필요가 없다.

     

     

     

     

    URLSearchParams

    formData 객체에 일일히 append 하여 폼 데이터 값을 구성하는 것이 번거로우면

    URLSearchParams() 을 사용하면 일반 객체형태를 `formdata`형식으로 자동 변환 해주어 보다 가독성 좋게 전송할 수 있다.

    fetch('https://httpbin.org/post', {
        method: 'POST',
        cache: 'no-cache',
        body: new URLSearchParams({ // 일반 객체를 fordata형식으로 변환해주는 클래스
            aaa: 'a1',
            bbb: 'b1'
        })
    })
    .then((response) => response.json())
    .then((data) => {
        console.log(data);
    });

     

     


     

    예제코드

    # multipart/form-data 클라이언트 데이터 형태 구성
    
    const data = new FormData();
        data.append('img', images[0]);
        data.append('type', 'board');
        data.append('service', 'premium');
     
    const result = await axios({
        method: 'post',
        url: '/papi/file/upload',
        data: data ,
        headers: {
          'Content-Type': `multipart/form-data`,
        },
     });

     


     

    formData를 이용한 투두리스트 예제(비제어 컴포넌트)

    import { useId, useState } from "react";
    
    const TodoForm = ({ setTodos }: TodoProps) => {
      const id = useId();
    
      const onClickSubmit = (e: React.ChangeEvent<HTMLFormElement>) => {
        e.preventDefault();
        const formData = new FormData(e.target);
        const title = formData.get("title") as string | null;
        const content = formData.get("content") as string | null;
    
        if (content && title) {
          const nextTodo = {
            id: crypto.randomUUID(),
            title,
            content,
            isDone: false,
          };
    
          setTodos((prevTodo) => [nextTodo, ...prevTodo]);
          e.target.reset();
        }
      };
    
      return (
        <div>
          <form onSubmit={onClickSubmit}>
            <div>
              <label htmlFor={`${id}-title`}>제목</label>
              <input
                type="text"
                placeholder="제목을 입력해 주세요."
                id={`${id}-title`}
                autoFocus
                name="title"
              />
              <p>{titleError}</p>
            </div>
            <div>
              <label htmlFor={`${id}-content`}>내용</label>
              <input
                type="text"
                placeholder="내용을 입력해 주세요."
                id={`${id}-content`}
                name="content"
              />
              <p>{contentError}</p>
            </div>
            <button type="submit">입력</button>
          </form>
        </div>
      );
    };
    
    export default TodoForm;

     

     

    ✏️ 코드 설명

    const formData = new FormData(e.target);
    const title = formData.get("title");
    const content = formData.get("content");

     

    form 태그에서 새로운 new FormData를 새로운 api에 들어와서 formData로 다시 탄생하게 된다.

    그럼 FormData를 관리하는게 쉬워진다.

    비제어 컴포넌트에서는 `name`만 넣어주면 제어가 가능하다. 

    button type을 `submit`으로 해주면 `onSubmit` 이 실행이 된다.

     

    `new FormData`를 통해서 이전에 만들어줬던 submit 데이터를 폼 데이터로 바꿔준다.

    FormData에서 타이틀과 컨텐츠를 가져온다. name을 바탕으로 가져오게 된다.

       if (content && title) {
          const nextTodo = {
            id: crypto.randomUUID(),
            title,
            content,
            isDone: false,
          };

     

    새로 만들어줄 todo에 객체를 생성을 해준다.

    그리고 prop으로 받아준 state를 콜백함수로 넣어줬다.

     

     

     

    setTodos((prevTodo) => [nextTodo, ...prevTodo]);
    e.target.reset();

     

    setTodos는 익명함수가 들어갔고, 기존의 값을 가져와서 변형시키기 위해서 콜백함수를 가져왔다. 

    useState의 비동기성 때문에 넣어준거기도 하고 props로 따로 todo를 받아오지 않기 위해서 사용했다.

    만약에 이렇게 안하면 todo 데이터를 props로 한개 더 가져와야 하는 번거로움도 있고, 잠재적인 오류의 위험도 있다. (버츄얼 돔과 연관되어있음)

     

    마지막에 초기화 해줘야하니 e.target.reset()을 사용했다.

    e.target.reset()은 확인 버튼을 눌렀을때 적용할 필요 없이 reset 하나만으로 비제어 컴포넌트는 이렇게 리셋해준다.

    폼태그를 리셋해준다는 뜻이다. 

    비제어 컴포넌트로 useState를 하나하나씩 써주는 것 보단 이렇게 써주는게 더 직관적으로 코드를 읽을 수 있다.

     

     

     


     

     

    Docs, 참고한 블로그

     

    FormData 객체

     

    ko.javascript.info

     

     

    🌐 FormData 사용법 & 응용 총정리 (+ fetch 전송)

    FormData API 보통 서버에 데이터를 전송하기 위해서는 HTML5 의 폼 태그를 사용해 다음과 같이 메뉴를 구성하여 제출 해본 기억들이 있을 것이다. 아이디 비밀번호 성별 남자 여자 응시분야 영어 수

    inpa.tistory.com

     

    댓글