写在本文之前,想一想我们常用的文件下载是怎样做的?
通过a标签
<a target="_blank" href="//www.xxx.com/download/file.txt" download="file">下载</a>
通过form表单
<form action='/download' method='post'>
<input type="text" name="name" value="file"/>
<button>下载</button>
</form>
这些都没有问题,包括使用条件筛选然后下载。但是如果需求是要在筛选前做条件验证呢? 相信这也难道不倒聪明的你。
我曾经写过一个相对“优雅”的a标签根据条件下载并作出反馈😏:
class DownloadPage extends Component {
constructor(props) {
super(props);
this.state = {
name: ''
};
}
onChange = (e) => {
const name = e.target.value;
this.setState({
name
});
}
onDownload = (e) => {
if (!this.state.name) {
window.alert('请先输入筛选条件');
e.preventDefault();
}
}
render() {
const { name } = this.state;
return (
<div>
<input value={name} onChange={(e) => this.onChange(e)} />
<a target="_blank" href={`//www.xxx.com/download?name=${name}`} onClick={() => this.onDownload()}>导出文件</a>
</div>
);
}
}
以上代码是react的例子,但是任何框架的实现都是如此,思路不会被框架所限制。
上文中的方式绝对能够满足绝大多数的业务需求了,但是这不符合本文的目的,我们今天要讲的是怎样通过ajax下载文件。
ajax下载文件
通常来说,ajax一般用来做数据交换。
想想吧,如果把上面例子中的条件放到另一个接口中验证。。。等到接口返回再e.preventDefault()
,事件早已触发默认行为并作出响应了。
ajax下载实现基础: ajax(responseType)、createObjectURL、Blob
ajax(responseType)
responseType顾名思义,是用来设置ajax返回数据的类型,允许的值有arraybuffer、blob、document、json、text,默认值为text,值得注意的是xhr.responseType设置必须在open之后,send之前调用。我们在这里需要设置:
xhr.responseType = 'arraybuffer'
这表明ajax将会接受一个arraybuffer类型的数据。
Blob
Blob
对象是一个不可变、原始数据的类文件对象。Blob 表示的不一定是JavaScript原生格式的数据。例如我们可以设置一个excel文件:
new Blob(bufferData, {type: 'application/vnd.ms-excel'})
navigator.msSaveBlob
msSaveBlob方法可以将js blob保存到电脑上,从而实现下载效果。
navigator.msSaveBlob(blob, fileName);
createObjectURL、revokeObjectURL
createObjectURL
的作用是创建一个新的对象URL,该对象URL可以代表某一个指定的file
对象或Blob
对像,也就是在内存中存了一个可访问的文件。需要注意的是文件存储在内存中占用过大,所以一定要记得revokeObjectURL
释放内存。
使用代码如下:
const objectURL = window.URL.createObjectURL(blob);
...
window.URL.revokeObjectURL(link.href)
具体实现
那么我们就很明白了,后端返回一个buffer,前端设置接收类型为arraybuffer,{ responseType: 'arraybuffer' }
,前端接收到到buffer对象。
通过buffer.data取到buffer值,通过blob设置type为文件格式。例如: new Blob([res.data], {type: 'application/vnd.ms-excel'})
,就是将文件设置为excel文件格式。
最后使用navigator.msSaveBlob保存为本地文件。
如果你的浏览器不支持msSaveBlob,你也可以通过 a 标签,设置href指向createObjectURL文件 window.URL.createObjectURL(blob)
,调用click导出。最后revokeObjectURL
释放文件内存。
示例
- 后端代码 (node)
const express = require('express')
const app = express()
app.post('*', (req, res) => {
res.send(new Buffer('this is a test demo'))
})
- 前端代码
const axios = require('axios')
let Service = axios.create({
timeout: 60 * 1000,
baseURL: 'http://www.xxx.com',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
Service.post('/xxx/xxx/importExcel', param, { responseType: 'arraybuffer' }).then((res) => {
const fileName = '测试文件'
const blob = new Blob([res.data], {type: 'application/vnd.ms-excel'})
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, fileName)
} else {
var link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
window.URL.revokeObjectURL(link.href)
}
})
通过这几行代码,我们就能直接接收后端接口返回的文件流了。
用这种方法实现下载的好处是不限制请求类型,也能保持相对干净的dom结(只需要一个按钮,剩下的都交给js去做吧!)。
结束语
当然本文所讲述到的api并不仅仅限于下载ajax文件,通过讲解的过程我们是不是学到了更多的api,前端操作文件真的是大有可玩。
比如说我们可以实现一个前端生成文件的工具,只需要使用blob + msSaveBlob/createObjectURL
就可以轻松做到。
const blob = new Blob(['这是一个前端文件'], {type: 'application/vnd.ms-excel'})
const fileName = '测试文件'
if (window.navigator.msSaveOrOpenBlob) {
navigator.msSaveBlob(blob, fileName)
} else {
var link = document.createElement('a')
link.href = window.URL.createObjectURL(blob)
link.download = fileName
link.click()
window.URL.revokeObjectURL(link.href)
}
怎么样,学到了吗?
--The End