본문 바로가기

Libraries/React

[React] 프로필 사진 업로더 만들기, 프로필 변경/삭제/추가, 프로필 페이지 만들기

728x90

 

리액트로 프로필 업로더를 만들어볼 것이다!
구현과정에 대한 설명을 세세하게 적어둔 블로그가 없었기에 많이 헤맸었다.
그래서 나는 최대한 친절하게, 자세히, 하나하나 다 정리해보고자 한다.

 

 


 

 

 

 1. 프로필 영역을 마크업 한다. 

 

 

 ProfileChange.jsx 

import styles from "./ProfileChange.module.css";
import DeleteImg from "../../../assets/my-page/setting/profile-delete.png";
import DefaultImg from "../../../assets/my-page/setting/default-background.png";

const ProfileChange = () => {

  return (
    <div className={styles["profile-setting-main-profile-change-container"]}>
      <p className={styles["profile-setting-main-profile-change-title"]}>프로필 변경</p>
      <div className={styles["profile-setting-main-profile-change-img-box"]}>
        <img src={DefaultImg} alt="프로필 사진" className={styles["profile-setting-main-profile-change-img"]} />
        <label className={styles["profile-setting-main-profile-change-add-img"]} htmlFor="input-file">
          <span className={styles["profile-setting-main-profile-change-add-img-icon"]}>블로그 이미지 찾아보기</span>
          <input
            type="file"
            accept="image/*"
            id="input-file"
            className={styles["profile-setting-main-profile-change-add-img-input"]}
          />
        </label>
        <button
          type="button"
          className={styles["profile-setting-main-profile-change-delete"]}
        >
        </button>
      </div>
    </div>
  );
};

export default ProfileChange;

 

프로필 사진 영역의 코드를 설명해보겠다.

<div>로 <img>와 <label>을 묶고, 
<img> 안에는 회색이미지 ( src={DefaultImg} ) 를 넣는다.

<label> 안에 <span>과 <input>을 넣는다. 
<label>의 htmlFor과 <input>의 id는 동일한 이름으로 연결해야함을 유의한다.

<span>안에는 background로 플러스 버튼 이미지를 넣는다.
그리고 '블로그 이미지 찾아보기'를 text-indent - 9999px로 설정하여 화면 상에서 안보이게 한다.


<input>은 type="file"로 설정하고, accept="image/*" 로 하여, 모든 종류의 파일을 허용할 수 있게 하였다.
<input>의 기본 CSS를 display: none 으로 없애고,
파일 찾기 버튼을 없애기 위해
profile-setting-main-profile-change-add-img-input::file-selector-button { display: none; }
으로 설정했다.

 

 


 

 

 2. 파일에서 찾은 이미지를 업로드하는 기능 구현하기 

 

이제 플러스 버튼을 누르면, 파일 찾기가 뜨고, 파일찾기에서 이미지를 선택하면 해당 이미지가 프로필사진으로 들어오게 해보자.

import styles from "./ProfileChange.module.css";
import {useRef, useState} from "react";
import DeleteImg from "../../../assets/my-page/setting/profile-delete.png";
import DefaultImg from "../../../assets/my-page/setting/default-background.png";

const ProfileChange = () => {
  const [Image, setImage] = useState(DefaultImg);  // (1)번 설명
  const [File, setFile] = useState("");  // (2)번 설명
  const fileInput = useRef(null);  // (3)번 설명


  const onChange = (e) => {  // (4)번 설명
    if (e.target.files[0]) {
      setFile(e.target.files[0]);
    } else {
      //업로드 취소할 시
      setImage(DefaultImg);
      return;
    }
    
    
    //화면에 프로필 사진 표시
    const reader = new FileReader();  // (5)번 설명
    reader.onload = () => {
      if (reader.readyState === 2) {
        setImage(reader.result);
      }
    };
    reader.readAsDataURL(e.target.files[0]);
  };
  
  
  return (
    <div className={styles["profile-setting-main-profile-change-container"]}>
      <p className={styles["profile-setting-main-profile-change-title"]}>프로필 변경</p>
      <div className={styles["profile-setting-main-profile-change-img-box"]}>
        <img src={Image} alt="프로필 사진" className={styles["profile-setting-main-profile-change-img"]} />
        <label className={styles["profile-setting-main-profile-change-add-img"]} htmlFor="input-file">
          <span className={styles["profile-setting-main-profile-change-add-img-icon"]}>블로그 이미지 찾아보기</span>
          <input
            type="file"
            accept="image/*"
            id="input-file"
            className={styles["profile-setting-main-profile-change-add-img-input"]}
            onClick={() => {
              fileInput.current.value = null;
              fileInput.current.click();
            }}
            ref={fileInput}
            onChange={onChange}
          />
        </label>
        <button
          type="button"
          className={styles["profile-setting-main-profile-change-delete"]}
        >
        </button>
      </div>
    </div>
  );
};

export default ProfileChange;

 

// (1) 프로필 사진을 표시할 창 부분의 상태관리  ▶  const [Image, setImage] = useState(DefaultImg); 

useState를 사용해서 state를 Image로 설정하고, 이것을 프로필 사진 img src에 넣어준다.
  img src={Image} 

그리고, 초기값은 DefaultImage(회색 배경)로 설정한다. 

 

 

// (2) 프로필창에 들어갈 파일 상태관리 ▶  const [File, setFile] = useState(""); 

 

 

// (3) Ref 객체 생성하기  ▶  const fileInput = useRef(null); 

이 Ref 객체를 선택하고 싶은 DOM에 (여기서는 <input>태그에) ref값으로 설정해준다.   ref={fileInput} 

<input>태그(플러스 버튼)에 onClick 이벤트를 넣어서, 사진을 클릭하면 파일 업로더를 띄울 수 있도록한다.
 onClick={() => { fileInput.current.value = null;  fileInput.current.click() }} 

 

 

// (4) onChange함수

  const onChange = (e) => { 
    if (e.target.files[0]) {
      setFile(e.target.files[0]);
    } else {
      //업로드 취소할 시
      setImage(DefaultImg);
      return;
    }

 

<input>에 onChange이벤트로 onChange함수를 달아준다.   onChange={onChange} 

파일을 찾아서 업로드를 성공적으로 완료를 하게되면, onChange함수의 if절이 실행돼서, setFile로 인해 상태변화가 일어나게되고, 파일이 input창으로 정상적으로 업로드된다.   setFile(e.target.files[0]) 

파일 업로드를 취소하면 else절이 실행돼서, 회색 배경 기본 이미지를 설정하도록 해준다.
  setImage(DefaultImg) 

 

 

// (5) FileReader

FileReader을 생성하고 이미지를 정상적으로 불러오면 이미지를 프로필 사진으로 지정한다.

readAsDataURL함수로 받아온 파일을 reader로 불러와준다.

    const reader = new FileReader();
    reader.onload = () => {
      if (reader.readyState === 2) { // 성공적으로 파일을 읽어들였을 때
        setImage(reader.result);
      }
    };
    reader.readAsDataURL(e.target.files[0]);
  };

 

첨부된 파일을 웹 화면에서 보기 위해서는, 파일을 File Reader를 사용하여 Data url 형식으로 변환해주어야 한다.

 

 Data Url 

Data URIs, 즉 data: 스킴이 접두어로 붙은 URL은 컨텐츠 작성자가 작은 파일을 문서 내에 인라인으로 임베드할 수 있도록 해준다.

 

 FileReader 

FileReader 객체는 웹 애플리케이션이 비동기적으로 데이터를 읽기 위하여 읽을 파일을 가리키는 File 혹은 Blob 객체를 이용해 파일의 내용을(혹은 raw data버퍼로) 읽고 사용자의 컴퓨터에 저장하는 것을 가능하게 해준다.

 

(1) FileReader.onload

FileReader가 성공적으로 파일을 읽어들였을 때 트리거 되는 이벤트 핸들러이다. 이 핸들러 내부에 우리가 원하는 이미지 프리뷰 로직을 넣어주면 된다.

 

(2) FileReader.readystate 

FileReader의 현재 상태를 나타낸다.

 

(3) FileReader.readAsDataURL()

readAsDataURL은 File 혹은 Blob 을 읽은 뒤 base64로 인코딩한 문자열을 FileReader 인스턴스의 result라는 속성에 담아준다. 이 메서드를 사용해 이미지를 base64로 인코딩하여 Image라는 state 안에 넣어주는 것이다.

 

 

 구현 모습 

 

플러스 버튼을 누르면 이렇게 파일 찾기가 뜬다.

 

 

 

 

 

 


 

 

 3. 파일 삭제하는 기능 구현하기 

 

import styles from "./ProfileChange.module.css";
import {useRef, useState} from "react";
import DeleteImg from "../../../assets/my-page/setting/profile-delete.png";
import DefaultImg from "../../../assets/my-page/setting/default-background.png";

const ProfileChange = () => {
  const [Image, setImage] = useState(DefaultImg);
  const [File, setFile] = useState("");
  const fileInput = useRef(null);

  const onChange = (e) => {
    if (e.target.files[0]) {
      setFile(e.target.files[0]);
    } else {
      setImage(DefaultImg);
      return;
    }

    const reader = new FileReader();
    reader.onload = () => {
      if (reader.readyState === 2) {
        setImage(reader.result);
      }
    };
    reader.readAsDataURL(e.target.files[0]);
  };
  return (
    <div className={styles["profile-setting-main-profile-change-container"]}>
      <p className={styles["profile-setting-main-profile-change-title"]}>프로필 변경</p>
      <div className={styles["profile-setting-main-profile-change-img-box"]}>
        <img src={Image} alt="프로필 사진" className={styles["profile-setting-main-profile-change-img"]} />
        <label className={styles["profile-setting-main-profile-change-add-img"]} htmlFor="input-file">
          <span className={styles["profile-setting-main-profile-change-add-img-icon"]}>블로그 이미지 찾아보기</span>
          <input
            type="file"
            accept="image/*"
            id="input-file"
            className={styles["profile-setting-main-profile-change-add-img-input"]}
            onClick={() => {
              fileInput.current.value = null;
              fileInput.current.click();
            }}
            ref={fileInput}
            onChange={onChange}
          />
        </label>
        <button
          type="button"
          className={styles["profile-setting-main-profile-change-delete"]}
          // (2) 번 설명
          onClick={() => {
            window.confirm("이미지를 삭제하겠습니까?") ? setImage(DefaultImg) : null;
          }}
        >
          // (1) 번 설명
          {Image === DefaultImg ? null : <img src={DeleteImg} alt="이미지 삭제" className={styles["profile-setting-main-profile-change-delete-img"]} />}
        </button>
      </div>
    </div>
  );
};

export default ProfileChange;

 

// (1) 이미지가 DefaultImg(기본 회색이미지)이면, 버튼이 뜨지 않게 하고,  DefaultImg(기본 회색이미지)가 아니면, 즉 사진이 업로드 된다면, "이미지 삭제 버튼 아이콘"이 우측 상단에 뜨게끔 한다. 

 {Image === DefaultImg ? null : <img src={DeleteImg} alt="이미지 삭제" className={styles["profile-setting-main-profile-change-delete-img"]} />} 

 

Image === DefaultImg가 true일 때
Image === DefaultImg가 false일 때

 

// (2) 삭제 버튼에 클릭이벤트를 달아서, 

삭제 버튼을 눌렀을 때 confirm창이 뜨고, 확인을 누르면 DefaultImg가 뜨게 하고, 취소를 누르면 원래 상태로 유지되게 한다.

  onClick={() => { window.confirm("이미지를 삭제하겠습니까?") ? setImage(DefaultImg) : null; }} 

 

 

 확인을 눌렀을 때 삭제되는 모습 

 

 

 

 취소를 눌렀을때 유지되는 모습 

 

 

 


 

 

 4. 완성된 프로필 업로더 

 

 

728x90