[TIL] Input 타입 File로 여러개 이미지 업로드와 미리보기 몇가지 예시..

소진수·2021년 8월 22일
2

HTML

목록 보기
3/3
post-thumbnail

🙆🏻‍♂️ 이미지 파일 업로더 & 이미지 파일 미리보기 제작과정


이미지를 업로드하는 방법

  • Input 태그의 Type을 File로 하여 업로드한다.

    • value = 파일 경로

      • 보안상 브라우젱서 직접 접근 X, c:/fakepath/르ㄹ 포함하여 숨김
    • accept : 이미지 유형 지정

      • Image/*: png 같은 이미지 파일
        • Image 대신 video, audio, / ".pdf, .doc, .csv" 등이 가능하다
    • Capture : 휴대폰일 시, 카메라 시작

      • Camera, camcorder, microphone 가능
    • Multiple : 여러개 이미지 등록가능

    • FileList: Array-like object로 File들을 가지고 있는 객체이다.

      • 이는 files type의 엘리먼트로 선택된 파일의 리스트에 접근할 수 있다.

      • 드래그 앤 드랍 API 사용할 때 파일의 리스트에도 사용됨 (DataTransfer객체 참조)

      • var file = document.getElementById('fileItem').files[0]

        위 같은 방식으로 파일에 접근할 수 있다.

      • 참조하기 위해서는 for of 혹은 Array.from() 로 변환참조 가능

    • FIle : 읽기 전용

      • 가지고 있는 속성
        • Name : 파일 이름
        • Size : 파일 크기
        • Type : MIME 유형

input File 타입 실행 과정

  • 로컬에서 파일을 선택한 파일은 File로 정의 / FilesList에 저장됨

  • 파일을 선택하면 input, change EventHandler 발생, 선택된 파일은 HTMLInputElement.files에 저장

    • onChnage는 포커스를 잃을 때 발생
    • onInput은 값이 변경된 직후 발생
      • 순서상 onInput이 먼저~
  • 업로드한 이미지를 미리보기 위해서는 FileReader 객체를 사용한다.

    • 웹 어플리케이션이 비동기적으로 데이터를 읽기 위하여 읽을 파일을 가리키는 File/Blob 객체를 이용해 파일 내용을 읽고 사용자의 컴퓨터에 저장한다
// state
state: {
  // 업로드한 파일 객체를 저장할 state
   detailImageFile: null,
  // 업로드한 파일 객체의 이미지 url을 저장할 state
   detailImageUrl: null,
}


// set file reader function : FileReader가 onload되면 FileReader의 result를 반환
   // setImageFromFile은 file, setImageUrl을 매개변수로 받는다
const setImageFromFile = ({ file, setImageUrl }) => {
  // reader에 빈 FileReader 객체를 선언한다.
   let reader = new FileReader();
  // FilerReader.onload : load 이벤트의 핸들로.
  // 해당 이벤트는 읽기 동작이 성공적으로 완료되었을 때마다 발생
  // 비동기여서 원하는 동작을 위해 callback으로 실행해야 한다.
  	// 읽기 동장이 성공적으로 완료되면 함수를 실행한다
   reader.onload = function () {
     // setImageUrl 함수는 result키에 reader(FileReader).result를 value 값으로 넣는다
     // reader.result = 파일의 내용을 반환한다.
      setImageUrl({ result: reader.result });
   };
   
  // FileReader의 readAsDataURL메서드 파일 내용 반환이 완료되면 그 파일의 URL을 가져온다
   reader.readAsDataURL(file);
};

  // 위 함수는 매개변수로 전달되는 file의 url을 받아오고 setImageUrl은 result 키 값에 reader.result를 준다
	
// render JSX
const fileInputArea = 
    <>
      <input
          type="file"
          id="detail_image"
          accept="image/*"
  				// 변화가 발생하면 callback 함수가 실행된다
  				// target(input.file)의 files객체 도달
  				// 이 사람은 바로 const { files } = event.target 을 이렇게 해버린 것이다
  					// {target: {files}}
          onChange={({ target: { files } }) => {
            // 만약 filesr가 있다면 (files의 method는 length 뿐)
              if (files.length) {
                // 위에서 정의한 SetImageFromFile 함수에서 구조분해를 해서 보냄
                	// File에 files[0]// setImageUrl에 매개변수 result도 구조분해 해버리네.. 
             				// 함수 실행하면 setState({detailImageFile: files[0], detailImageUrl: result})한다...
                  setImageFromFile({
                      file: files[0],
                      setImageUrl: ({ result }) => setState({detailImageFile: files[0], detailImageUrl: result});
              }
          }}
      />
      //detailImageFile이 있다면
        // src에 변경된 스테이트 값을 넣는다
      {detailImageFile && (
        <div className="image_area">
          <img src={detailImageUrl} alt={detailImageFile.name} />
        </div>
      )}
    </>
  • 내가 원했던 기능을 만든 사람의 코드
  // useState 빈배열을 만듬 '선택된 파일들'
	const [selectedFiles, setSelectedFiles] = useState([]);
	
	// 이벤트를 매개변수로 받는 함수 정의
  const handleImageChange = e => {
    // e.target.files가 있는지 찍어봄
    console.log(e.target.files);
    // 만약 이벤트 타겟의 파일들이 있다면
    if (e.target.files) {
      // filesArry 변수를 선언한다. 그 변수에는
      // e.target.files를 배열로 만듬
      	// Array.from = 유사배열 / 반복가능 객체를 얕게 복사해 새로운 Array객체로 만드는 것이다
        // 그래서 맵을 돌려서 각 배열의 파일이 포함된 배열을 리턴한다.
      	// CreateObjectURL은 주어진 객체를 가리키는 URL을 DOMString으로 반환
      		// 해당 URL은 자신을 생성한 창의 document가 사라지면 함께 무효화
       		// 객체 URL을 해제하기 위해서 revokeObjectURL()을 호출
      const filesArray = Array.from(e.target.files).map(file =>
        URL.createObjectURL(file)
      );
			// filesArray에 맵으로 돌린 URL들이 잘 담겨있는지 확인
      console.log('filesArray: ', filesArray);

      // setSelectedFiles(usestate)로 스테이트 업데이트
      // prevImages로 전 이미지들을 얕은 복사해서 가져오고 그 배열에 새롭게 추가된 filesArray를 추가
      setSelectedFiles(prevImages => prevImages.concat(filesArray));
      // 그리고 다시한번 Array.from(e.target.files).map을 해서 file 객체의 URL을 해제한다.
        // 이건 계속 메모리 누수가 발생하는걸 없애기 위해서 하는 것이다.
        // 만약 revokeObjectURL을 하지않으면 해당 파일의 URL을 브라우저가 계속 저장한다.
        // 하지만 위에서 State에 파일을 concat해줬기 때문에 더이상 브라우저에서 해당 URL을 지니지 않아도 된다.
      Array.from(e.target.files).map(
        file => URL.revokeObjectURL(file) 
      );
    }
  };

	// renderPhotos라는 함수를 선언한다.
  // 이 함수는 나중에 이미지가 렌더되는 곳에서 state를 받아서 맵으로 돌려서 보여주는 기능을 한다.
  const renderPhotos = source => {
    console.log('source: ', source);
    return source.map(photo => {
      return <PreviewImage src={photo} alt="" key={photo} />;
    });
  };
  ...
	<input onchange={handleImageChange}>
  ...
  {renderPhotos(selectedFiles)}

  
  • 업로드한 이미지는 FormData 형태로 S3에 업로드한다
    • Key: image
    • Value: File Addres
1차 시도
const onImgChange = async(event: any) => {
  setLogoLoading(true);
  const formData = new FormData();
  formData.append('image', event.target.files[0]);
  const response = await apiClient.post('/brand/logo_image', formData);
  // reponse.data.location이 업로드한 파일의 url
  setLogoLoading(false);
}

2차 시도
const setFile = e => {
  const formData = new FormData();
  formData.append('image', ImageForUpload);
  axios.post('public/data/SellerUpload.json', formData);
};
  • Delete 버튼 만들기

    • Delete 버튼을 클릭하면 버튼도 사라지고 이미지도 사라지고 데이터도 사라져야한다...
    • 이 모든걸 충족하기 위해선 < button, image, data >를 한번에 다뤄야한다.
      • filter함수를 통해서 조건에 맞지 않는 배열을 다시 전달하고 그 배열이 데이터로 전달되어야 한다
  • example

    const App = () => {
    // 새로운 배열 스테이트를 만든다
    const [pics, setPics] = useState([]);
    //  버튼을 클릭 하면 id 값으로 특정하여 가져오게 했다.
    const removeImage = (id) => {
    // 새롭게 스테이트를 변경한다.
    // 매개변수를 주고 매개변수를 필터로 돌려서 아이템을 리턴하는데 id가 맞으면 제외한다.
    setPics((oldState) => oldState.filter((item) => item.id !== id));
    };
    
    useEffect(() => {
    // 기존에 이미지를 가져오는 함수
    setPics(allImages);
    }, []);
    return (
    <div className="App">
    	// 맵함수로 데이터 있는데로 돌린다
    {pics.map((pic) => {
      return (
        <div style={{ marginBottom: "100px" }}>
        	// 이미지 데이터의 아이디를 보여주고
          {pic.id}
          <img
            src={pic.imgUrl}
            width="100px"
            height="100px"
            alt="placeholder grey 100px"
          />
          // 버튼을 클릭하면 비동기로 removeImage(pic.id)가 전달되어 작동
          <button onClick={() => removeImage(pic.id)}>X</button>
        </div>
      );
    })}
    </div>
    );
    };
    
    export default App;
profile
느려서 바쁘다

0개의 댓글