[웨일마켓] 회원가입 프로필 이미지 업로드 및 미리보기

MOON HEE·2022년 7월 20일
0

트러블 슈팅

목록 보기
18/25
post-thumbnail

회원가입을 위해 필요한 6개 유저 정보(email, password, username, accountname, intro, image) 중 image를 전달하는 과정을 정리했다.
(나머지 정보들은 input에서 바로 받아오는 값이기 때문에 그렇게 어렵지 않다)

📈 전체적인 흐름


moonhee.jpg ▶ 123123.jpg ▶ API/123123.jpg ▶ 문자열(AAASDEEEAFSDQ08AZCBCNVZRNZRRREE)



프로필 컴포넌트의 return은 아래와 같다. Wrapper로 label를 감싸고 그 안에 img태그가 있다. 이 img 태그는 프로필 이미지 우측 하단의 작은 동그라미 아이콘이다.
그리고 Wrapper 밖에는 input 태그가 있다. 이 input 태그는 file 타입이다. 파일을 하나 혹은 여러 개 선택할 수 있게 해주는 기능이 있다. 이 input 태그에 id와 Wrapper 안에 있는 label의 htmlFor을 profileImg로 동일하게 줌으로써 두 요소가 연결되게 해놨다. 이렇게 한 이유는 기본적으로 제공되는 input 디자인 말고 다른 이미지를 쓰고 싶기 때문이다. input을 숨기고 두 요소를 연결해줌으로써 label 안의 아이콘을 누르면 input의 기능이 동작하게 된다.

return (
  ...
<Wrapper ref={previewImage}>
    <Label htmlFor="profileImg">
        <Img src={upload_icon} alt="프로필 이미지 업로드"/>
    </Label>
</Wrapper>
<Input 
    type="file" 
    accept="image/*" 
    id="profileImg"
    onChange={handleImageChange}
/>
  ...

1. 이미지를 서버에 전송 & 숫자로 이루어진 filename 응답받기

언뜻 생각했을 때는 이미지를 선택해서 브라우저에 보이게 하는 거니까 서버에 뭔가를 전송해야한다고 생각하지 못했다. 먼저 서버에 전송해야 하는 이유는 최종적으로 회원가입 요청을 보낼 때 이 이미지를 문자열로 전송해야 하기 때문이다.

예를 들면, 이런 형태로 최종적으로 회원가입 폼에 담아 보내야 한다.

API/1111111111.png

최종적으로 이렇게 나와야 한다는 걸 알았으니, 이제 '1111111111.png'와 같이 파일 이름을 숫자로 바꿔주기 위해 이미지를 서버에 전송해보자.

const [image, setImage] = useState('기본값으로 들어갈 이미지 URL');
const previewImage = useRef();

async function onLoadImage (formData, loadImage) {
    try {
        const config = {
            headers: {
                "Content-Type": "multipart/form-data",
            },
        };
        const response = await axios.post(
            `${API_URL}/image/uploadfile`,
            formData,
            config
        );
        // 응답에 파일이름이 있으면, image 값을 API + filename으로 지정하고
        // files정보(loadImage)를 가지고, 이미지를 보이게 해주는 함수를 실행한다
        if (response?.data?.filename) {
            setImage(`${API_URL}/` + response?.data?.filename);
            preview(loadImage);
        } else {
            alert('.jpg, .gif, .png, .jpeg, .bmp, .tif, .heic 파일만 업로드 가능합니다.');
        }
    } catch (error) {
        console.error(error);
        alert('잘못된 접근입니다.');
    }
};

위 코드의 image는 최종적으로 회원가입 폼에 담겨서 전달할 image의 값이다. 그리고 이미지를 감싸고 있는 Wrapper에 접근하기 위해 useRef를 사용한다. Wrapper의 background-image를 조정해서 선택한 이미지가 보이게 만들거기 때문이다.

onLoadImage는 formData와 loadImage라는 인자를 갖는다. formData는 폼 데이터 객체를 의미하고 loadImage는 files 정보이다(2.에 관련내용 나옴).

POST 요청을 보내기 위해 본문에 담길 FormData와 헤더에 담길 config가 필요하다. formData는 2.에서 알아보고 config부터 살펴보면, Body에 들어갈 데이터인 formData의 타입을 명시하기 위해 사용한다. form은 컨트롤 요소로 구성된다.

  • name: form의 이름. 서버로 보낼 때 이 이름으로 데이터가 전송된다.
  • action: form이 전송되는 서버 URL 또는 HTML 링크
  • method: 전송 방법 설정(기본값: GET)
  • autocomplete: 자동완성 여부
  • entype: 폼 데이터가 서버로 제출될 때 해당 데이터가 인코딩 되는 방법

마지막 entype의 값 중에 하나가 multipart/form-data이다. 이 값은 모든 문자를 인코딩하지 않음을 명시하는데, 이 방식은 form 요소가 파일이나 이미지를 서버로 전송할 때 주로 사용한다.

전송 후 if 문으로 response에 filename이 있으면 image값을 API/filename으로 변경해주고, 브라우저에 보이게 만들어주는 preview라는 함수에 loadImage(input의 files 정보)를 담아 실행시켜준다.


2. 그 전에 formData 객체에 파일정보를 담는 과정이 있다

function handleImageChange (event) {
    const loadImage = event.target.files;
    const formData = new FormData();   
    formData.append('image', loadImage[0]);
    onLoadImage(formData, loadImage);
}

최종적인 이미지 URL을 그리기 전에 필요한 인자인 formDataloadImage를 정의하는 구간이다. 순서상으로는 1. 보다 앞이지만 거꾸로 올라가는게 더 도움이 될 것 같아서 이 순서로 구성했다.

loadImage라는 변수에 files 정보들을 담아준다. files는 file 타입의 input이 가지고 있는 file에 대한 정보가 담겨있다(로컬에 저장되어 있는 '파일이름.확장자' 등). 그리고 새로운 formData 객체를 생성한다. 이 객체에 image라는 이름의 값으로 loadImage[0], 즉 0번째 파일을 지정해준다.
formData(폼데이터객체)와 loadImage(files 정보)를 가지고 이미지를 서버에 전송하는 함수인 onLoadImage를 실행한다(1. 내용).

3. 미리보기 기능

function preview(loadImage) {
    const reader = new FileReader();
    reader.onload = () => ( 
        previewImage.current.style.backgroundImage = `url(${reader.result})`
    );
    reader.readAsDataURL(loadImage[0]);
};

loadImage(input의 files 정보들)를 가지고 미리보기 기능을 만든다.
FileReader라는 생성자 함수를 하나 만든다. FileReader는 파일의 내용을 읽고 사용자의 컴퓨터에 저장하는 생성자 함수이다.
reader.onload는 읽기 동작이 성공적으로 완료될 때마다 실행된다. input의 files 정보들을 성공적으로 읽었다면 Wrapper에 접근해서 background-image를 변경시킬 수 있는 코드다.
돔을 조작하고 나면 reader.readAsDataURL 메서드를 사용하여 result 속성에 문자열 데이터(엄청나게 김)가 담기게 한다.


💻 회원가입 폼 제출

const reqData = {
    user: { 
        username: username,
        email: auth.email,
        password: auth.password,
        accountname: accountname,
        intro: intro,
        image: image
    }
};

이렇게 image 한 글자에 문자열 데이터를 넣기 위해 복잡한 과정을 거쳤다. 선택한 파일을 폼 데이터에 담아 제출하기 위해서는 문자열(엄청나게 길다)로 담아야 하고 문자열로 담으려면 API 뒤에 숫자.확장자 형식으로 만들어 주고 이렇게 만드려면 input의 file 정보를 가져와야 한다.
이미지가 한개라서 다행이지, 여러개는 또 어떻게 할까 모르겠다...

profile
자고 일어나면 해결되는게 더 많은 듯 그럼 잘까

0개의 댓글