最近寫了一個react的組件,用來作文件導出。環境是ie10+。
細一點說,就是javascript
一、讀取form裏的數據html
二、向服務端發請求,並下載文件;要求拿到請求狀態,若是出錯及時反饋給用戶。html5
第一個需求,咱們借用了jquery的serializeArray方法,畢竟咱們不想再造輪子。那接下來重點說說後面的需求。java
你們在下載的問題的時候,通常來講,會用到react
一、window.open(url);jquery
二、window.location.href = url;web
三、iframe,其實與window.open相似,但不用開啓新的tabajax
四、a 標籤,利用download屬性chrome
這些方法,其實極度依賴服務端的正確性。咱們能夠看看服務端一旦出錯的結果。api
一、window.open(url)
打開一個帶錯誤信息的頁面
二、window.location.href
頁面將跳轉到一個錯誤頁面
三、iframe
用戶感知不到任何變化
四、a標籤
直接出現 下載失敗
固然,若是response header裏有content-disposition字段的話,瀏覽器都會下載一個帶錯誤信息的文件。這時候,其實咱們能夠多發一個ajax/fetch請求,先檢測下接口狀態,而後再取作下載邏輯。但這樣就對服務器形成了額外的開銷。
這樣的體驗都不太好,做爲一個追求極致體驗的程序猿,咱們應該從新思考下,如何提高用戶體驗。
相信你們對blob這個對象也不太陌生,它是html5標準裏的一個二進制數據對象,能夠與URL 對象配合,進行文件的下載。
下面是一個最簡單的demo(咱們暫時不考慮瀏覽器兼容問題)。
let blob2 = new Blob(['123']); let url = URL.createObjectURL(blob2); let a = document.createElement('a'); a.download = 'test'; a.href = url; a.click();
其實這樣一個簡單的demo,就能夠實現瀏覽器端本身的下載了。那如何從服務端拿到數據,並下載呢?
這裏,咱們拿服務端數據,主要是經過fetch,fetch提供了一些api。其中就有一個blob的promise,咱們能夠把返回的數據轉成blob對象,這樣就能去下載文件了。
回到最初的需求,咱們須要檢測接口的狀態。其實經過fetch,咱們徹底能夠拿到response的信息,既然都能拿到,那控制權就在咱們自手上了。
按照FileApi的方式,咱們是一次性從服務端拿到數據,而後再在瀏覽器端進行操做。既然是這樣,那拿數據的過程是否是就能夠顯示出進度呢?這個特性,咱們用之前傳統的下載方式是徹底作不到的。
關於進度條,咱們能夠利用fetch配合reader對象來實現進度條功能,以下:
fetch(url).then(response => { var reader = response.body.getReader(); var headers = response.headers; var totalLength = headers.get('Content-Length'); var bytesReceived = 0; reader.read().then(function processResult(result) { if (result.done) { return; } bytesReceived += result.value.length; console.log(`progress: ${bytesReceived / totalLength * 100}%`); return reader.read().then(processResult); }); });
固然,有人可能會說ie下fetch會有問題。沒錯,確實會有問題,但這時候咱們能夠用XMLHttpRequest這個對象來實現,會更簡單直接一點。
經過fileapi的方式下載文件,有個很重要的問題,就是文件名。最初的一些下載方式,都是瀏覽器本身經過判斷content-disposition這個字段來讀取文件名。那如今不同了,咱們須要本身來讀取文件名,這時候不免要本身讀這個header,經過正則匹配下文件名。
關於cors問題,其實只要是異步請求,都會碰到。新版瀏覽器,咱們經常使用access-control-allow-origin這個字段來解決跨域問題。這時候,咱們在讀文件名的可能要留一點,記得在header里加上Access-Control-Expose-Headers這個字段,否則fetch是取不到filename信息的。具體能夠看看這篇doc
大文件下載問題
在用fileapi的時候,咱們發現文件過大會讓瀏覽器崩潰,會致使文件下載失敗。目前我在測試500mb以上的文件的時候就會碰到這樣的狀況。這個問題,能夠經過webkitrequestfilesystem這個對象來曲線解決,但這並非一個standard api,目前只有新版chrome支持這個對象,因此儘可能不要去用。
咱們推薦在拿到response的時候,讀取一下blob的size,若是發現太大,就進行降級處理,使用咱們最初的那中方式。
我一直相信no silver bullet這句話,雖然fileapi這種方式能解決部分問題,但其實也有不少缺點,相信你們會在實際場景中會更深入的感覺到。因此在設計組件的時候,咱們在作好優雅降級的方案同時,還特地爲你們開放了各類下載方式,以適應各類場景。