Blob을 활용해서 파일 다운로드 기능을 구현해보자!

💛 nalsae·2024년 2월 14일
6

🔥 트러블 슈팅

목록 보기
7/7
post-thumbnail

 얼마 전 진행한 사내 프로젝트에서 가공된 문자열을 사용자가 텍스트 파일로 다운로드하는 기능을 구현해야 할 일이 있었다. 구현이 완료된 모습은 다음과 같다.

 기능 구현은 크게 어렵지 않다. 버튼 컴포넌트에 클릭 이벤트 핸들러를 등록하고, 핸들러의 콜백 함수에 파일을 생성한 후 다운로드하는 로직을 작성해주면 된다. 크게 복잡한 내용은 없으므로 바로 코드를 살펴보겠다.


🔍 구현 코드 뜯어보기

// useDownloadText.ts
export const useDownloadText = () => {
  const handleDownloadText = () => {
    // 1. 파일 이름 설정
    const fileName = 'text.txt';
    // 2. 임시 <a> 요소 생성
    const element = document.createElement('a');
    // 3. 텍스트 파일 생성
    const file = new Blob([text], {
      type: 'text/plain',
    });

    // 4. 생성한 파일의 URL을 <a> 요소 href 값으로 지정
    element.href = URL.createObjectURL(file);
    // 5. 생성한 파일의 이름을 <a> 요소 download 값에 지정 
    element.download = fileName;
    // 6. <a> 요소를 DOM 트리에 삽입
    document.body.appendChild(element);

    // 7. <a> 요소 클릭 처리
    element.click();
    
    // 8. DOM 트리에서 <a> 요소 제거
    element.remove();
  };
  
  return { useDownloadText };
}; 

 상단 코드를 보면 알 수 있듯 다운로드 로직은 따로 useDownloadText이라는 커스텀 훅을 생성하여 분리했다. 관심사의 분리를 위해 컴포넌트 파일에 포함할 필요가 없기도 하고, 컴포넌트 파일에 다운로드 로직을 포함하면 가독성 관점에서도 좋지 않다고 판단했기 때문이다.


💡 알고 가면 좋은 개념들

📌 Blob

 지금부터는 상단 코드에서 특징적인 점 몇 가지만 살펴보고자 한다. 먼저 Blob이다. 사실 이번에 처음 접한 개념은 아니다. 이전에 진행했던 "Grow Story" 프로젝트에서도 사용자가 등록한 이미지 파일을 토대로 서버에 요청을 보낼 때 Blob을 사용했다. 그렇다면 이 Blob은 당최 무엇일까? Blob은 Binary Large Object의 약자로, 이름 그대로 이진 데이터를 저장하는 용량이 큰 객체를 의미한다. 큰 용량을 저장할 수 있기 때문에 주로 이미지, 오디오, 영상 등의 데이터를 다룰 때 사용하는 편이다. 그렇다고 해서 멀티미디어 데이터만 저장할 수 있는 것은 아니고, HTML 문서나 텍스트 등 이진 데이터 형식으로 표현할 수 있는 다양한 데이터를 Blob 객체에 저장할 수 있다. Blob 관련 내용은 하단 MDN 문서에 더 자세하게 기술되어 있다.

📝 Blob이란?

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

 기능 구현과 관련된 내용만 간단하게 살펴보면, 먼저 생성자 함수 Blobnew 키워드와 함께 호출하여 Blob 인스턴스를 생성할 수 있다. 호출 시 필수적으로 첫 번째 인수를 전달해야 하는데, Blob 객체로 저장할 데이터를 배열에 담아 전달하면 된다. 두 번째 인수는 옵셔널한 값으로 저장할 데이터의 MIME 타입을 객체에 담아 전달할 수 있다. 하단 예제처럼 말이다. 텍스트 파일이 아니더라도 호출 시 전달하는 인수를 변경하여 이미지, 오디오 등 다양한 파일을 Blob 인스턴스로 생성할 수 있을 것이다.

const blob = new Blob([저장할 데이터], { type: 'MIME type 문자열' });

📌 MIME type

 여기서 잠깐 상술한 내용에 등장한 MIME type이라는 개념을 잠깐 살펴보자면, 데이터를 송수신할 때 그 데이터의 형식을 나타내기 위해 기재하는 타입이다. text/plain, image/png 등이 그에 해당하며 자세한 내용은 하단의 관련 문서에 기술되어 있다.

📝 MIME types(Media Types)

: https://www.iana.org/assignments/media-types/media-types.xhtml

📌 URL.createObjectURL

 다음으로 살펴볼 내용은 URL API의 createObjectURL 메서드이다. 다운로드에 필요한 링크가 존재하지 않으면 생성한 Blob 인스턴스는 그냥 데이터를 한 번 감싼 선물 상자에 불과하다. 사용자가 이를 참조할 수 있도록 URL을 생성하여 인스턴스에 매핑해줄 필요가 있다. 이때 사용하는 메서드가 바로 createObjectURL이다. MDN 문서를 잠깐 살펴보자.

URL.createObjectURL()

URL.createObjectURL() 정적 메서드는 주어진 객체를 가리키는 URL을 DOMString으로 반환합니다. 해당 URL은 자신을 생성한 창의 document가 사라지면 함께 무효화됩니다.

 createObjectURL은 정적 메서드이므로 따로 인스턴스를 생성하지 않고 빌트인 URL 객체에서 바로 참조 가능하다. 호출 시에는 인수를 전달해야 하는데, 생성할 URL을 매핑해줄 객체를 전달하면 된다. 호출 후에는 반환된 URL 문자열을 <a> 요소의 href 어트리뷰트 값으로 지정해주면 끝이다. 구체적인 예제는 하단에 작성해보았다.

const element = document.createElement('a');
const blob = new Blob([저장할 데이터], { type: 'MIME type 문자열' });

element.href = URL.createObjectURL(blob);

📌 <a> 요소의 download 어트리뷰트

 또 하나 특징적인 부분이 있다면 임시로 생성한 <a> 요소에 지정하는 download 어트리뷰트일 것이다. 긴 말 필요없이 MDN 문서를 살펴보자.

download

링크로 이동하는 대신 사용자에게 URL을 저장할지 물어봅니다. 값을 지정할 수도 있고, 지정하지 않을 수도 있습니다.
값이 없으면 파일 이름과 확장자는 브라우저가 다양한 인자로부터 생성해 제안합니다.
값을 지정하면 저장할 때의 파일 이름으로서 제안합니다. /와 \ 문자는_로 변환합니다. 파일시스템에서 다른 문자도 제한할 수 있으므로, 필요한 경우 브라우저가 추가로 이름을 조정할 수 있습니다.

 MDN 문서에도 잘 나와있듯 <a> 요소에 download 어트리뷰트를 지정하면 페이지를 이동할 수 있는 링크가 아니라 특정 데이터를 다운로드할 수 있는 링크가 된다. 이때 어트리뷰트의 값은 옵셔널하게 지정할 수 있는데, 프로젝트에서는 고정된 파일 명을 지정할 필요가 있었기 때문에 값을 따로 지정해주었다.

📌 DOM 트리에서 <a> 요소 제거하기

 마지막으로 생성한 <a> 요소를 DOM 트리에 삽입하여 클릭한 후 제거하는 로직이다. 만약 제거하는 로직이 존재하지 않으면 다운로드 완료 후 DOM 트리에 불필요한 임시 <a> 요소가 잔재하게 된다. 그 불편한 상황을 하단 이미지에서 확인할 수 있다.


😆 Blob, 정리 완료!

 여기까지 Blob을 활용하여 사용자가 파일을 다운로드할 수 있는 기능을 구현하면서 관련 개념들을 살펴보았다. 이전 프로젝트를 진행할 때 스치듯 지나간 개념들이지만, 글을 작성하면서 따로 리서치하는 중에 더 확실하게 정립할 수 있는 것 같다.

🙏 출처

https://developer.mozilla.org/ko/docs/Web/API/Blob
https://developer.mozilla.org/ko/docs/Web/API/URL/createObjectURL_static
https://developer.mozilla.org/ko/docs/Web/HTML/Element/a

profile
𝙸'𝚖 𝚊 𝚍𝚎𝚟𝚎𝚕𝚘𝚙𝚎𝚛 𝚝𝚛𝚢𝚒𝚗𝚐 𝚝𝚘 𝚜𝚝𝚞𝚍𝚢 𝚊𝚕𝚠𝚊𝚢𝚜. 🤔

0개의 댓글