目录一、需求场景二、实现原理三、React 实现步骤1. 托管静态资源2. 封装hook3. 使用hook四、Vue 实现步骤1. 托管静态资源2. 封装hook3. 使用hook五
下载服务端大文件资源过慢,页面没有任何显示,体验太差。因此需增加进度条优化显示
发送异步Http请求,监听onprogress事件,读取已下载的资源和资源总大小得到下载百分比
在资源请求完成后,将文件内容转为blob,并通过a标签将文件通过浏览器下载下来
前提:通过create-react-app创建的react项目
将静态资源文件放到public文件夹下,这样启动项目后,可直接通过http://localhost:3000/1.pdf 的方式访问到静态资源。在实际工作中,肯定是直接访问服务器上的资源
新建useDownload.ts
import { useCallback, useRef, useState } from 'react';
interface Options {
fileName: string; //下载的文件名
onCompleted?: () => void; //请求完成的回调方法
onError?: (error: Error) => void; //请求失败的回调方法
}
interface FileDownReturn {
download: () => void; //下载
cancel: () => void; //取消
progress: number; //下载进度百分比
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); //默认异步请求
xhr.responseType = 'blob';
xhr.onprogress = (e) => {
//判断资源长度是否可计算
if (e.lengthComputable) {
const percent = Math.floor((e.loaded / e.total) * 100);
setProgress(percent);
}
};
xhr.onload = () => {
if (xhr.status === 200) {
//请求资源完成,将文件内容转为blob
const blob = new Blob([xhr.response], { type: 'application/octet-stream' });
//通过a标签将资源下载
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(); //发送请求
setProgress(0); //每次发送时将进度重置为0
setIsDownloading(true);
}, [fileName, onCompleted, onError, url]);
const cancel = useCallback(() => {
xhrRef.current?.abort(); //取消请求
setIsDownloading(false);
}, [xhrRef]);
return {
download,
cancel,
progress,
isDownloading,
};
}
import { memo } from 'react';
import useFileDown from './useDownload';
const list = [
{
fileName: '城市发展史起.pdf',
url: ' http://localhost:3000/1.pdf',
type: 'pdf',
},
{
fileName: '表格.xlsx',
url: 'http://localhost:3000/表格.xlsx',
type: 'xlsx',
},
{
fileName: '报告.doc',
url: 'http://localhost:3000/报告.doc',
type: 'doc',
},
];
interface Options {
url: string;
fileName: string;
}
const Item = memo(({ url, fileName }: Options) => {
//每项都需拥有一个属于自己的 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;
前提:通过vite创建的vue项目
将静态资源文件放到public文件夹下,这样启动项目后,可直接通过http://127.0.0.1:5173/1.pdf 的方式访问到静态资源
新建hooks/useDownload.ts(新建hooks文件夹)
import { ref } from "vue";
export interface Options {
fileName: string;
onCompleted?: () => void; //请求完成的回调方法
onError?: (error: Error) => void; //请求失败的回调方法
}
export interface FileDownReturn {
download: () => void; //下载
cancel: () => void; //取消
progress: number; //下载进度百分比
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); //默认异步请求
xhr.responseType = "blob";
xhr.onprogress = (e) => {
//判断资源长度是否可计算
if (e.lengthComputable) {
const percent = Math.floor((e.loaded / e.total) * 100);
progress.value = percent;
}
};
xhr.onload = () => {
if (xhr.status === 200) {
//请求资源完成,将文件内容转为blob
const blob = new Blob([xhr.response], {
type: "application/octet-stream",
});
//通过a标签将资源下载
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(); //发送请求
progress.value = 0; //每次发送时将进度重置为0
isDownloading.value = true;
};
const cancel = () => {
xhrRef.value?.abort(); //取消请求
isDownloading.value = false;
};
return {
download,
cancel,
progress,
isDownloading,
};
}
<script setup lang="ts">
import Item from "./components/Item.vue";
const list = [
{
fileName: "城市发展史起.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: "报告.doc",
url: "http://127.0.0.1:5173/报告.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>
<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>
解决办法:让后端加上就行
开启gzip之后服务器默认开启文件分块编码(响应头返回Transfer-Encoding: chunked)。分块编码把「报文」分割成若干个大小已知的块,块之间是紧挨着发送的。采用这种传输方式进行响应时,不会传Content-Length这个首部信息,即使带上了也是不准确的
分别为gzip压缩,分块编码:
例如有个877k大小的js文件,网络请求的大小为247k。但是打印的e.loaded最终返回的是877k
解决方法:后端把文件大小存储到其他字段,比如:header['x-content-length']
到此这篇关于React和Vue实现文件下载进度条的文章就介绍到这了,更多相关React Vue下载进度条内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!
--结束END--
本文标题: React和Vue实现文件下载进度条
本文链接: https://lsjlt.com/news/211605.html(转载时请注明来源链接)
有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341
2024-01-12
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
2023-05-20
回答
回答
回答
回答
回答
回答
回答
回答
回答
回答
0