React和Vue實(shí)現(xiàn)文件下載進(jìn)度條
一、需求場(chǎng)景
下載服務(wù)端大文件資源過(guò)慢,頁(yè)面沒(méi)有任何顯示,體驗(yàn)太差。因此需增加進(jìn)度條優(yōu)化顯示
二、實(shí)現(xiàn)原理
發(fā)送異步HTTP請(qǐng)求,監(jiān)聽(tīng)onprogress事件,讀取已下載的資源和資源總大小得到下載百分比
在資源請(qǐng)求完成后,將文件內(nèi)容轉(zhuǎn)為blob,并通過(guò)a標(biāo)簽將文件通過(guò)瀏覽器下載下來(lái)
三、react 實(shí)現(xiàn)步驟
1. 托管靜態(tài)資源
前提:通過(guò)create-react-app創(chuàng)建的react項(xiàng)目
將靜態(tài)資源文件放到public文件夾下,這樣啟動(dòng)項(xiàng)目后,可直接通過(guò)http://localhost:3000/1.pdf 的方式訪問(wèn)到靜態(tài)資源。在實(shí)際工作中,肯定是直接訪問(wèn)服務(wù)器上的資源
2. 封裝hook
新建useDownload.ts
import { useCallback, useRef, useState } from 'react';
interface Options {
fileName: string; //下載的文件名
onCompleted?: () => void; //請(qǐng)求完成的回調(diào)方法
onError?: (error: Error) => void; //請(qǐng)求失敗的回調(diào)方法
}
interface FileDownReturn {
download: () => void; //下載
cancel: () => void; //取消
progress: number; //下載進(jìn)度百分比
isDownloading: boolean; //是否下載中
}
export default function useFileDown(url: string, options: Options): FileDownReturn {
const { fileName, onCompleted, onError } = options;
const [progress, setProgress] = useState(0);
const [isDownloading, setIsDownloading] = useState(false);
const xhrRef = useRef<XMLHttpRequest | null>(null);
const download = useCallback(() => {
const xhr = (xhrRef.current = new XMLHttpRequest());
xhr.open('GET', url); //默認(rèn)異步請(qǐng)求
xhr.responseType = 'blob';
xhr.onprogress = (e) => {
//判斷資源長(zhǎng)度是否可計(jì)算
if (e.lengthComputable) {
const percent = Math.floor((e.loaded / e.total) * 100);
setProgress(percent);
}
};
xhr.onload = () => {
if (xhr.status === 200) {
//請(qǐng)求資源完成,將文件內(nèi)容轉(zhuǎn)為blob
const blob = new Blob([xhr.response], { type: 'application/octet-stream' });
//通過(guò)a標(biāo)簽將資源下載
const link = document.createElement('a');
link.href = window.URL.createObjectURL(blob);
link.download = decodeURIComponent(fileName);
link.click();
window.URL.revokeObjectURL(link.href);
onCompleted && onCompleted();
} else {
onError && onError(new Error('下載失敗'));
}
setIsDownloading(false);
};
xhr.onerror = () => {
onError && onError(new Error('下載失敗'));
setIsDownloading(false);
};
xhrRef.current.send(); //發(fā)送請(qǐng)求
setProgress(0); //每次發(fā)送時(shí)將進(jìn)度重置為0
setIsDownloading(true);
}, [fileName, onCompleted, onError, url]);
const cancel = useCallback(() => {
xhrRef.current?.abort(); //取消請(qǐng)求
setIsDownloading(false);
}, [xhrRef]);
return {
download,
cancel,
progress,
isDownloading,
};
}
3. 使用hook
import { memo } from 'react';
import useFileDown from './useDownload';
const list = [
{
fileName: '城市發(fā)展史起.pdf',
url: ' http://localhost:3000/1.pdf',
type: 'pdf',
},
{
fileName: '表格.xlsx',
url: 'http://localhost:3000/表格.xlsx',
type: 'xlsx',
},
{
fileName: '報(bào)告.doc',
url: 'http://localhost:3000/報(bào)告.doc',
type: 'doc',
},
];
interface Options {
url: string;
fileName: string;
}
const Item = memo(({ url, fileName }: Options) => {
//每項(xiàng)都需擁有一個(gè)屬于自己的 useFileDown hook
const { download, cancel, progress, isDownloading } = useFileDown(url, { fileName });
return (
<div>
<span style={{ cursor: 'pointer' }} onClick={download}>
{fileName}
</span>
{isDownloading ? (
<span>
{`下載中:${progress}`}
<button onClick={cancel}>取消下載</button>
</span>
) : (
''
)}
</div>
);
});
const Download = () => {
return (
<div>
{list.map((item, index) => (
<Item url={item.url} fileName={item.fileName} key={index} />
))}
</div>
);
};
export default Download;
四、vue 實(shí)現(xiàn)步驟
1. 托管靜態(tài)資源
前提:通過(guò)vite創(chuàng)建的vue項(xiàng)目
將靜態(tài)資源文件放到public文件夾下,這樣啟動(dòng)項(xiàng)目后,可直接通過(guò)http://127.0.0.1:5173/1.pdf 的方式訪問(wèn)到靜態(tài)資源
2. 封裝hook
新建hooks/useDownload.ts(新建hooks文件夾)
import { ref } from "vue";
export interface Options {
fileName: string;
onCompleted?: () => void; //請(qǐng)求完成的回調(diào)方法
onError?: (error: Error) => void; //請(qǐng)求失敗的回調(diào)方法
}
export interface FileDownReturn {
download: () => void; //下載
cancel: () => void; //取消
progress: number; //下載進(jìn)度百分比
isDownloading: boolean; //是否下載中
}
export default function useFileDown(
url: string,
options: Options
): FileDownReturn {
const { fileName, onCompleted, onError } = options;
const progress = ref(0);
const isDownloading = ref(false);
const xhrRef = ref<XMLHttpRequest | null>(null);
const download = () => {
const xhr = (xhrRef.value = new XMLHttpRequest());
xhr.open("GET", url); //默認(rèn)異步請(qǐng)求
xhr.responseType = "blob";
xhr.onprogress = (e) => {
//判斷資源長(zhǎng)度是否可計(jì)算
if (e.lengthComputable) {
const percent = Math.floor((e.loaded / e.total) * 100);
progress.value = percent;
}
};
xhr.onload = () => {
if (xhr.status === 200) {
//請(qǐng)求資源完成,將文件內(nèi)容轉(zhuǎn)為blob
const blob = new Blob([xhr.response], {
type: "application/octet-stream",
});
//通過(guò)a標(biāo)簽將資源下載
const link = document.createElement("a");
link.href = window.URL.createObjectURL(blob);
link.download = decodeURIComponent(fileName);
link.click();
window.URL.revokeObjectURL(link.href);
onCompleted && onCompleted();
} else {
onError && onError(new Error("下載失敗"));
}
isDownloading.value = false;
};
xhr.onerror = () => {
onError && onError(new Error("下載失敗"));
isDownloading.value = false;
};
xhrRef.value.send(); //發(fā)送請(qǐng)求
progress.value = 0; //每次發(fā)送時(shí)將進(jìn)度重置為0
isDownloading.value = true;
};
const cancel = () => {
xhrRef.value?.abort(); //取消請(qǐng)求
isDownloading.value = false;
};
return {
download,
cancel,
progress,
isDownloading,
};
}
3. 使用hook
- 修改App.vue
<script setup lang="ts">
import Item from "./components/Item.vue";
const list = [
{
fileName: "城市發(fā)展史起.pdf",
url: " http://127.0.0.1:5173/1.pdf",
type: "pdf",
},
{
fileName: "表格.xlsx",
url: "http://127.0.0.1:5173/表格.xlsx",
type: "xlsx",
},
{
fileName: "報(bào)告.doc",
url: "http://127.0.0.1:5173/報(bào)告.doc",
type: "doc",
},
];
</script>
<template>
<div>
<div v-for="(item, index) in list" :key="index">
<Item :url="item.url" :fileName="item.fileName"<script setup lang="ts">
import useFileDown from "../hooks/useDownload.ts";
const props = defineProps<{ url: string; fileName: string }>();
const { url, fileName } = props;
const { download, cancel, progress, isDownloading } = useFileDown(url, {
fileName,
});
</script>
<template>
<div>
<span style="cursor: pointer" @click="download">
{{ fileName }}
</span>
<span v-if="isDownloading">
下載中:{{ progress }} <button @click="cancel">取消下載</button></span
>
</div>
</template> />
</div>
</div>
</template>
- 新建components/Item.vue
<script setup lang="ts">
import useFileDown from "../hooks/useDownload.ts";
const props = defineProps<{ url: string; fileName: string }>();
const { url, fileName } = props;
const { download, cancel, progress, isDownloading } = useFileDown(url, {
fileName,
});
</script>
<template>
<div>
<span style="cursor: pointer" @click="download">
{{ fileName }}
</span>
<span v-if="isDownloading">
下載中:{{ progress }} <button @click="cancel">取消下載</button></span
>
</div>
</template>
五、可能遇到的問(wèn)題:lengthComputable為false
原因一:后端響應(yīng)頭沒(méi)有返回Content-Length;
解決辦法:讓后端加上就行
原因二:開(kāi)啟了gzip壓縮
開(kāi)啟gzip之后服務(wù)器默認(rèn)開(kāi)啟文件分塊編碼(響應(yīng)頭返回Transfer-Encoding: chunked)。分塊編碼把「報(bào)文」分割成若干個(gè)大小已知的塊,塊之間是緊挨著發(fā)送的。采用這種傳輸方式進(jìn)行響應(yīng)時(shí),不會(huì)傳Content-Length這個(gè)首部信息,即使帶上了也是不準(zhǔn)確的
分別為gzip壓縮,分塊編碼:

例如有個(gè)877k大小的js文件,網(wǎng)絡(luò)請(qǐng)求的大小為247k。但是打印的e.loaded最終返回的是877k

解決方法:后端把文件大小存儲(chǔ)到其他字段,比如:header['x-content-length']
到此這篇關(guān)于React和Vue實(shí)現(xiàn)文件下載進(jìn)度條的文章就介紹到這了,更多相關(guān)React Vue下載進(jìn)度條內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React Native react-navigation 導(dǎo)航使用詳解
本篇文章主要介紹了React Native react-navigation 導(dǎo)航使用詳解,詳解的介紹了react-navigation導(dǎo)航的使用,具有一定的參考價(jià)值,有興趣的可以了解一下2017-12-12
React渲染機(jī)制及相關(guān)優(yōu)化方案
這篇文章主要介紹了react中的渲染機(jī)制以及相關(guān)的優(yōu)化方案,內(nèi)容包括react渲染步驟、concurrent機(jī)制以及產(chǎn)生作用的機(jī)會(huì),簡(jiǎn)單模擬實(shí)現(xiàn) concurrent mode,基于作業(yè)調(diào)度優(yōu)先級(jí)的思路進(jìn)行項(xiàng)目?jī)?yōu)化的兩個(gè)hooks,感興趣的小伙伴跟著小編一起來(lái)看看吧2023-07-07
ReactNative實(shí)現(xiàn)圖片上傳功能的示例代碼
本篇文章主要介紹了ReactNative實(shí)現(xiàn)圖片上傳功能的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。2017-07-07
淺談redux以及react-redux簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了淺談redux以及react-redux簡(jiǎn)單實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08
react-dnd實(shí)現(xiàn)任意拖動(dòng)與互換位置
這篇文章主要為大家詳細(xì)介紹了react-dnd實(shí)現(xiàn)任意拖動(dòng)與互換位置,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08

