ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • FileReader multiple files 처리하기
    Languages/JavaScript 2023. 12. 3. 13:55

    최근 다수의 이미지 파일을 업로드하여 바로 사용자가 볼 수 있도록 하는 Form을 만들게 되었습니다. FileReader를 이용한 file reading 방식으로 개발을 진행하였고 이번 기회에 해당 내용을 포스팅하게 되었습니다.

     

    FileReader single file case

    다수의 파일을 읽는 케이스를 설명하기 전에 FileReader를 이용한 단일 파일의 케이스 먼저 설명하겠습니다. 업로드된 이미지 파일의 읽기 위해 FileReader의 메서드인 readAsDataURL()을 사용합니다. 이미지의 경우 인코딩하지 않을 경우 깨지기 때문에 readAsDataURL()를 통해 base64로 인코딩 된 데이터로 반환받아 사용합니다. FileReader의 콜백처리를 위해 Promise를 사용합니다.

     

    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Single File Reader</title>
      </head>
      <body>
        <div>Single File Reader</div>
        <input type="file" id="file-input"/>
        <div class="img-section"></div>
        <script>
          function encodeFileToBase64(file) {
            const reader = new FileReader();
            reader.readAsDataURL(file);
    
            return new Promise((resolve) => {
              reader.onload = function() {
                resolve(reader.result);
              };
            });
          }
    
          document.getElementById('file-input').addEventListener(
            'change',
            (e) => {
              const file = e.currentTarget.files[0];
              if (!file) return;
              encodeFileToBase64(file).then((fileContent) => {
                const image = document.createElement('img');
                image.src = fileContent;
                document
                  .getElementsByClassName('img-section')[0]
                  .appendChild(image);
              });
            },
            false,
          );
        </script>
      </body>
    </html>

     

     

    // React case
    import { useRef, useState } from 'react';
    
    import Button from '@/components/Button';
    
    function SingleFileReader() {
      const [image, setImage] = useState<string>('');
      const fileEl = useRef<HTMLInputElement>(null);
    
      const encodeFileToBase64 = (file: File) => {
        const reader = new FileReader();
        reader.readAsDataURL(file);
    
        return new Promise<void>((resolve) => {
          reader.onload = () => {
            setImage(reader.result as string);
            resolve();
          };
        });
      };
    
      const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        if (e.target.files && e.target.files.length) {
          const file = e.target.files[0];
          const formData = new FormData();
          formData.append('file', file);
          encodeFileToBase64(file);
        }
      };
    
      return (
        <div>
          <input
            type="file"
            ref={fileEl}
            hidden
            accept="image/*"
            onChange={handleOnChange}
          />
          <Button onClick={() => fileEl.current?.click()}>
            파일 선택
          </Button>
          {!!image && <img width={80} height={80} src={image} />}
        </div>
      );
    }
    
    export default SingleFileReader;

     

    FileReader multiple files case

    다수의 파일의 경우도 단일 파일의 케이스와 같은 과정을 가집니다. 단일 파일의 케이스와 다른 점은 다수의 파일을 읽어 들이는 각각의 Promise를 배열에 담아 Promise.all을 통해 일괄적으로 처리합니다.

     

    <!DOCTYPE html>
    <html lang="ko">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Multiple File Reader</title>
      </head>
      <body>
        <div>Multiple File Reader</div>
        <input type="file" id="file-input" multiple />
        <div class="img-section"></div>
        <script>
          function encodeFileToBase64(files) {
            const encodingFiles = files.map((file) => {
              const reader = new FileReader();
              reader.readAsDataURL(file);
              return new Promise((resolve) => {
                reader.onload = () => {
                  resolve(reader.result);
                };
              });
            });
            return Promise.all(encodingFiles);
          }
    
          document.getElementById('file-input').addEventListener(
            'change',
            (e) => {
              const files = [...e.currentTarget.files];
              if (!files.length) return;
              encodeFileToBase64(files).then((fileContents) => {
                fileContents.forEach((content) => {
                  const image = document.createElement('img');
                  image.src = content;
                  document
                    .getElementsByClassName('img-section')[0]
                    .appendChild(image);
                });
              });
            },
            false,
          );
        </script>
      </body>
    </html>

     

    // React case
    import { useRef, useState } from 'react';
    
    import Button from '@/components/Button';
    
    function MultipleFilesReader() {
      const [images, setImages] = useState<Array<string>>([]);
      const fileEl = useRef<HTMLInputElement>(null);
    
      const encodeFileToBase64 = async (fileList: Array<File>) => {
        const encodingFiles = fileList.map((file) => {
          const reader = new FileReader();
          reader.readAsDataURL(file);
          return new Promise((resolve) => {
            reader.onload = () => {
              resolve(reader.result);
            };
          });
        });
    
        const convertFiles = await Promise.all(encodingFiles);
        setImages([...images, ...convertFiles] as Array<string>);
      };
    
      const handleOnChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        if (e.target.files && e.target.files.length) {
          const files = [...e.target.files];
          const formData = new FormData();
          files.forEach((file) => formData.append('file', file));
          encodeFileToBase64(files);
        }
      };
    
      return (
        <div>
          <input
            type="file"
            ref={fileEl}
            hidden
            multiple
            accept="image/*"
            onChange={handleOnChange}
          />
          <Button onClick={() => fileEl.current?.click()}>
            파일 선택
          </Button>
          {images.length > 0 && (
            <div>
              {images.map((image, idx) => (
                <img key={idx} width={80} height={80} src={image} />
              ))}
            </div>
          )}
        </div>
      );
    }
    
    export default MultipleFilesReader;

     

    마무리

    추가로 에러케이스 정리도 필요하지만 간략하게 FileReader를 이용한 예제를 통해 확인했습니다. FileReader의 존재만 알고 있다면 파일을 읽는 로직을 쉽게 구현이 가능했습니다. FileReader의 다른 메서드를 활용하여 다양하게 파일을 읽을 수 있기 때문에 필요에 따라 활용하면 되겠습니다.

     


    참고 자료

    https://developer.mozilla.org/ko/docs/Web/API/FileReader

    https://ourcodeworld.com/articles/read/1438/how-to-read-multiple-files-at-once-using-the-filereader-class-in-javascript

Designed by Tistory.