[개발 기록] FileReader.readAsDataURL vs URL.createObjectURL

블루·2022년 11월 14일
0

1. 비교하기

(1). 시간

  • URL.createObjectURL은 동기적으로 실행된다.(즉시 실행)
  • FileReader.readAsDataURL은 비동기적으로 실행된다.(시간이 조금 지체된 후 실행)

(2). 메모리 사용

  • URL.createObjectURL : hash 형태의 url을 반환해주고, revokeObjectURL 메서드가 실행되거나 브라우저가 닫히는 이벤트가 트리거 되기 전까지 메모리에 객체를 저장된다.
  • FileReader.readAsDataURL : base64로 인코딩된 값을 반환해준다. Blob URL(createObjectURL)에 비해 메모리를 많이 잡아먹지만, 사용하지 않으면 자동으로 가비지 컬렉터에 의해 제거된다.

(3). 지원

  • URL.createObjectURL : IE10 & 모든 모던 브라우저

  • FileReader.readAsDataURL : IE10 & 모든 모던 브라우저


결론

createObjectURL을 사용하는 게 효율적이고 빠르지만, 사용하지 않을 때 일일이 revokeObjectURL로 release시켜주어야 하는 번거로움이 있다.


2. 구현

(1) FileReader.readAsDataURL

  • App.tsx
    import React, { useState } from 'react';
    import styled from '@emotion/styled';
    import { pdfjs } from 'react-pdf';
    import 'react-pdf/dist/esm/Page/TextLayer.css';
    import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
    
    import PdfViewer from './components/PdfViewer';
    
    pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
    
    const Container = styled.div`
      box-sizing: border-box;
      width: 100%;
      height: 100vh;
      display: flex;
      flex-direction: column;
      justify-content: center;
    `;
    
    const ViewerContainer = styled.div`
      display: flex;
      justify-content: center;
    `;
    
    const ButtonContainer = styled.div`
      height: 60px;
      display: flex;
      justify-content: center;
      align-items: center;
      margin-top: 35px;
    `;
    
    const UploadButton = styled.label`
      border-radius: 15px;
      width: 150px;
      height: 60px;
      background-color: skyblue;
      font-size: 20px;
      font-weight: 700;
      cursor: pointer;
      display: flex;
      justify-content: center;
      align-items: center;
    
      &:hover {
        opacity: 0.5;
      }
    `;
    
    function App() {
      const [pdfUrl, setPdfUrl] = useState<string | undefined>(
        undefined
      );
    
      const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        if (e.target.files && e.target.files[0]) {
          const file = e.target.files[0];
          if (file.name.split('.').pop()?.toLowerCase() !== 'pdf') {
            alert('에러가 발생하였습니다.');
            return;
          }
          const fileReader = new FileReader();
          fileReader.onload = () => {
            if (typeof fileReader.result === 'string') {
              setPdfUrl(fileReader.result);
            }
          };
          fileReader.readAsDataURL(file);
        }
      };
      return (
        <Container>
          <ViewerContainer>
            <PdfViewer fileUrl={pdfUrl} />
          </ViewerContainer>
          <ButtonContainer>
            <UploadButton>
              업로드
              <input type="file" accept=".pdf" style={{ display: 'none' }} onChange={onFileChange} />
            </UploadButton>
          </ButtonContainer>
        </Container>
      );
    }
    
    export default App;
  • pdfViewer.tsx
    import React, { useState } from 'react';
    import styled from '@emotion/styled';
    import { Document, Page } from 'react-pdf';
    
    const Container = styled.div`
      overflow-y: scroll;
      height: 600px;
    `;
    
    interface IProps {
      readonly fileUrl: string | undefined;
    }
    
    const PdfViewer: React.FC<IProps> = ({ fileUrl }) => {
      const [numPages, setNumPages] = useState<number>(0);
    
      const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
        setNumPages(numPages);
      };
    
      return (
        <Container>
          <Document file={fileUrl} onLoadSuccess={onDocumentLoadSuccess}>
            {new Array(numPages).fill(null).map((element: null, index: number) => (
              <Page pageIndex={index} width={600} key={index} />
            ))}
          </Document>
        </Container>
      );
    };
    
    export default PdfViewer;

(2) URL.createObjectURL

  • App.tsx
    import React, { useState } from 'react';
    import styled from '@emotion/styled';
    import { pdfjs } from 'react-pdf';
    import 'react-pdf/dist/esm/Page/TextLayer.css';
    import 'react-pdf/dist/esm/Page/AnnotationLayer.css';
    
    import PdfViewer from './components/PdfViewer';
    
    pdfjs.GlobalWorkerOptions.workerSrc = `//cdnjs.cloudflare.com/ajax/libs/pdf.js/${pdfjs.version}/pdf.worker.js`;
    
    const Container = styled.div`
      box-sizing: border-box;
      width: 100%;
      height: 100vh;
      display: flex;
      flex-direction: column;
      justify-content: center;
    `;
    
    const ViewerContainer = styled.div`
      display: flex;
      justify-content: center;
    `;
    
    const ButtonContainer = styled.div`
      height: 60px;
      display: flex;
      justify-content: center;
      align-items: center;
      margin-top: 35px;
    `;
    
    const UploadButton = styled.label`
      border-radius: 15px;
      width: 150px;
      height: 60px;
      background-color: skyblue;
      font-size: 20px;
      font-weight: 700;
      cursor: pointer;
      display: flex;
      justify-content: center;
      align-items: center;
    
      &:hover {
        opacity: 0.5;
      }
    `;
    
    function App() {
      const [pdfUrl, setPdfUrl] = useState<string | undefined>(
        undefined
      );
    
      const onFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        if (e.target.files && e.target.files[0]) {
          const file = e.target.files[0];
          if (file.name.split('.').pop()?.toLowerCase() !== 'pdf') {
            alert('에러가 발생하였습니다.');
            return;
          }
    
          const blob = new Blob([file]);
          const pdfUrl = URL.createObjectURL(blob);
          setPdfUrl(pdfUrl);
        }
      };
    
      return (
        <Container>
          <ViewerContainer>
            <PdfViewer fileUrl={pdfUrl} />
          </ViewerContainer>
          <ButtonContainer>
            <UploadButton>
              업로드
              <input type="file" accept=".pdf" style={{ display: 'none' }} onChange={onFileChange} />
            </UploadButton>
          </ButtonContainer>
        </Container>
      );
    }
    
    export default App;
  • pdfViewer.tsx
    import React, { useState, useEffect } from 'react';
    import styled from '@emotion/styled';
    import { Document, Page } from 'react-pdf';
    
    const Container = styled.div`
      overflow-y: scroll;
      height: 600px;
    `;
    
    interface IProps {
      readonly fileUrl: string | undefined;
    }
    
    const PdfViewer: React.FC<IProps> = ({ fileUrl }) => {
      const [numPages, setNumPages] = useState<number>(0);
    
      const onDocumentLoadSuccess = ({ numPages }: { numPages: number }) => {
        setNumPages(numPages);
      };
    
      useEffect(() => {
        return () => {
          if (fileUrl !== undefined) {
            URL.revokeObjectURL(fileUrl);
          }
        };
      }, [fileUrl]);
    
      return (
        <Container>
          <Document file={fileUrl} onLoadSuccess={onDocumentLoadSuccess}>
            {new Array(numPages).fill(null).map((element: null, index: number) => (
              <Page pageIndex={index} width={600} key={index} />
            ))}
          </Document>
        </Container>
      );
    };
    
    export default PdfViewer;
    • 언마운트 될 때, revokeObjectURL을 통해 url의 Object를 release해줌


구현 결과

result



참고 자료

profile
개발 일지를 작성합니다

0개의 댓글