JavaScript-實作下載檔案的方式

趁早填坑。

簡述

在實際介紹下載檔案的方法前,我覺得先認識底下幾個東西會比較好上手:

  • <input type="file"> 的使用方式
  • window.URL.createObjectURL 的用法
  • Blob 資料格式和轉換格式的觀念

看完後你就會覺得下載檔案並沒有想像中那麼困難囉。

先從 HTML 標籤開始

要讓使用者上傳檔案的話基本上都會透過 <input type="file"> 來實作,他的基本用法是像這樣:

1
<input type="file" multiple accept="image/*" />
1
2
3
document.querySelector('input').addEventListener('change', function () {
console.log(this.files)
})

輸出結果:

file-object

懶人包:

  1. input 可以用來讓使用者上傳檔案,依據需求可以添加 multiple 來決定使否能多選?accept 決定只能選擇哪些 MIME 類型的檔案
  2. input 節點會有 files 這個屬性,就是 File 物件(儲存檔案的資訊)

createObjectURL

他是一個 HTML5 的 API,我們先來看它在文件上的定義:

Object URL is a URL representing a object. The URL lifetime is tied to the document in the window on which it was created. The new object URL represents the specified File object or Blob object. — MDN

簡單來說他是用來「建立 URL 的一個物件」,但這個物件的值必須是「File」或是「Blob」。

File 可以透過 <input type="file"> 上的 files 屬性來取得,而 Blob 則是一種資料格式。如果目前不太懂這兩個東西也沒關係,後面會再解釋,只要先記得下面這個原則就好:

  • 輸入:File / Blob
  • 輸出:URL

重點在於輸出URL,因為是 URL 的關係,所以只要是任何使用 URL 的標籤都可以拿來用,像是 <img><a>(通常是拿來做下載功能)。

這邊有一個很經典的例子:如果我想實作預覽圖片的功能怎麼做?這很常出現在「換頭像」的時候。

順道一提這種作法是有好處的,可以參考這篇:HTML5 神奇的 Object URL:不用後端,前端便能產生獲取指定物件的網址

只要透過 createObjectURL 可以很輕鬆的做到這件事:

1
2
3
<div class="container mt-5">
<input type="file" accept="image/*" />
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
document.querySelector('input').addEventListener('change', function () {
// reset screen
document.querySelector('p')?.remove()
document.querySelector('img')?.remove()
// show message when no file selected
if (this.files.length === 0) {
const p = document.createElement('p')
p.innerText = 'Not select any files'
document.querySelector('.container').append(p)
return
}
// show preview img
const img = document.createElement('img')
const url = window.URL.createObjectURL(this.files[0])
img.src = url
img.classList.add('img')
document.querySelector('.container').append(img)
})

實際範例可以參考這裡

關於 Blob 與 File

Blob 其實就是一種「資料格式」,Blob 是縮寫,全名為「Binary Large Object(二進位大型物件)」,而 File 是基於 Blob 來製作的另一種資料格式,所以本質上是差不多的東西。

這就跟 JSON 是一樣的道理,兩個都只是用來表示資料的一種「格式」,不用想得太複雜。

不過我們都知道碰到 JSON 時我們要用 JSON.parse 來解析,那 Blob 呢?

這邊要先說一個重要觀念:

JS 本身就是只能操作 JavaScript,而 Blob 沒辦法像 JSON 一樣可以轉換成 JavaScript,所以你是沒辦法直接用 JS 來操作 Blob 的。

你唯一能做的就是透過 Blob 來產生一段 url,接著再綁到對應的 HTML 標籤上來做處理(下載 or 顯示內容),畢竟 blob 的用途主要就是拿來處理檔案。

所以這邊先講步驟,待會再來示範:

  1. 建立 Blob 物件
  2. 透過 createObjectURL 來建立一個屬於 blob 的 URL
  3. 把 URL 綁到相關的 HTML 標籤上

這邊我要示範的是把一份 JSON 資料轉成 Blob,接著再把這個 Blob 轉成連結來打開它:

1
2
3
4
5
6
7
8
9
10
const jsonData = JSON.stringify({ hello: 'blob' }, null, 2)
// 1. create blob
const jsonBlob = new Blob([jsonData], { type: 'application/json' })
// 2. create url
const url = window.URL.createObjectURL(jsonBlob)
// 3. bind to dom
const a = document.createElement('a')
a.href = url
a.innerText = 'JSON'
document.body.append(a)

附註:實際示範可以參考這邊

補充一下 new Blob 的部分:

簡單來說他會接收兩個參數,第一個是「你要轉換的資料(必須放在陣列裡)」,第二個則是「MIME Type(簡單來說就是檔案類型啦)」,所以 new Blob([jsonData], { type: 'application/json' }) 的意思就是:

  • [jsonData] = 要轉換的資料
  • { type: 'application/json' } = MIME type

至於詳細的 MIME 列表可以參考 MDN

實作下載檔案

其實主要的概念就是前面說的 new BlobcreateObjectURL ,所以這邊就直接附上 code:

(這邊是透過 API 來取得對應的 Excel 檔案)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
api
.exportFile(requestData)
.then((response: any) => {
// 檔案名稱
const fileName = response.headers['content-disposition'].split('filename=')[1]
const blob = new Blob([response.data], { type: response.headers['content-type'] })
const url = window.URL.createObjectURL(blob)
const link = document.createElement('a')
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
// dowload
link.click()
link.remove()
})
.catch((error: any) => {
Notification('error', error.message)
})

這段的背後其實只是把 blob 產生的網址拿來寫入這段 HTML 後再幫你自動點擊:

1
2
3
4
<a 
href="blob:http://localhost:2051/4db51291-7057-4975-a6d5-727d0084c093"
download="IR_Export_20220719113912.xlsx">
</a>

附註:當 <a>dowload 屬性時就等於下載檔案

所以下載檔案的實作還蠻單純的,只要搞清楚資料格式怎麼轉換就好了。順道一提,如果檔案是透過 AJAX 來取得的話,請務必:

  • 在 header 加上 responseType = 'blob'
  • 在 header 加上 responseType = 'blob'
  • 在 header 加上 responseType = 'blob'

這樣子才會把 response 轉成 blob 的形式。看很多人都被這個雷到,所以特別強調一下。

Vue-element-plus 環境配置 TypeScript-複習與一些小技巧
Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×