[기능구현] 이미지 프리뷰 방식 비교 URL.createObjectURL vs FileReader.readAsDataURL

윤지·2025년 9월 3일

글 업로드 할 때 이미지 프리뷰를 보여주기위해서는 URL.createObjectURL vs FileReader.readAsDataURL 두 가지 방법을 활용할 수 있다. 이걸 비교해 보고자 한다

1. 비교하기

1-1. 데이터 형식

  • URL.createObjectURL: 브라우저 메모리 내 객체를 참조하는 임시 blob URL 생성
    ex) blob:https://example.com/550e8400...
    - 이 URL은 현재 브라우저 탭/세션 내에서만 유효, 새로고침하거나 탭 닫으면 사라짐
    - 장점: base64 변환이 필요 없어서 빠르고 메모리 효율적.
    - 단점: 외부 저장 불가. 서버로 보내거나 LocalStorage에 그대로 보관할 수 없음.
    (서버에 보내려면 원본 Blob/File을 직접 FormData로 append해서 보내야 함)
    - 주의: 사용 끝나면 URL.revokeObjectURL(url)로 직접 해제 필요(이후 예제 참고)
  • FileReader.readAsDataURL: 파일 내용을 base64 문자열로 변환해 "data:..." 형식으로 생성
    ex) data:image/jpeg;base64,/9j/4AAQSkZJRgAB...
    - 장점: 브라우저의 메모리 외부에서도 사용 가능. ⇒ 로컬 스토리지에 저장, 서버 전송, 다른 컨텍스트(다른 탭, 다른 브라우저)에서도 그대로 사용 가능
    - 단점: base64 인코딩이라 용량이 33% 정도 커지고 메모리 소모도 큼.

1-2. 시간

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

1-3. 메모리

  • URL.createObjectURL : 직접 revokeObjectURL로 해제 필요(또는 문서 종료 시 해제)
  • FileReader.readAsDataURL : 결과가 긴 문자열이라(base64) Blob URL(createObjectURL)에 비해 메모리를 많이 잡아먹지만, 사용하지 않으면 자동으로 가비지 컬렉터에 의해 자동 제거

1-4. 지원

  • 두 방식 모두 IE10+ 및 모던 브라우저 OK.

1-5. 결론

  • 미리보기(Preview) 용도라면 createObjectURL이 가볍고 빠름.
    단,
    사용하지 않을 때 일일이 revokeObjectURL로 release시켜주어야 하는 번거로움이 있다.
  • Base64(data URL) 은 “문자열이 필요할 때” 쓰면 좋음. (예: LocalStorage 임시 저장/복사-붙여넣기/서버가 Base64만 받을 때)
    이미지 크기 커질수록 손해(33%↑) 이고, 인코딩 CPU/메모리 비용도 들기 때문에 createObjectURL을 권장한다고 함. gpt왈

2. 예제 코드

URL.createObjectURLURL.revokeObjectURL

import { useEffect, useRef, useState } from "react";

export default function ImagePreview() {
  const [previewUrl, setPreviewUrl] = useState<string | null>(null);

  const handleChangeImage = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    // 새 URL 만들기 전에 이전 URL 해제
    setPreviewUrl((prev) => {
      if (prev) URL.revokeObjectURL(prev);
      return URL.createObjectURL(file);
    });
  };

  // 컴포넌트 언마운트 시 마지막 URL 해제
  useEffect(() => {
    return () => {
      if (previewUrl) URL.revokeObjectURL(previewUrl);
    };
  }, [previewUrl]);

  return (
    <>
      <input type="file" accept="image/*" onChange={handleChangeImage} />
      {previewUrl && (
        <img
          src={previewUrl}
          alt="preview"
          // 이미 로딩이 끝났다면 바로 해제하는 패턴도 가능(선택)
          onLoad={() => {
            // URL.revokeObjectURL(previewUrl); // 선택사항
          }}
        />
      )}
    </>
  );
}

FileReader.readAsDataURL

import { useState } from "react";

export default function ImagePreviewBase64() {
  const [previewDataUrl, setPreviewDataUrl] = useState<string | null>(null);

  const handleChangeImage = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    const reader = new FileReader(); //파일 리더 객체 지정
    reader.readAsDataURL(file);//파일 객체를 읽어서 이해 가능한 문자열로 변경
    reader.onload = () => {
      // onload 시점에만 result가 채워짐(비동기)
      setPreviewDataUrl(reader.result as string);
    };
  };

  return (
    <>
      <input type="file" accept="image/*" onChange={handleChangeImage} />
      {previewDataUrl && <img src={previewDataUrl} alt="preview" />}
    </>
  );
}
profile
프론트엔드 공부 기록 아카이빙🍁

0개의 댓글