Javascript 如何在react as文件中下载fetch响应
下面是Javascript 如何在react as文件中下载fetch响应,javascript,reactjs,flux,reactjs-flux,Javascript,Reactjs,Flux,Reactjs Flux,下面是actions.js export function exportRecordToExcel(record) { return ({fetch}) => ({ type: EXPORT_RECORD_TO_EXCEL, payload: { promise: fetch('/records/export', { credentials: 'same-origin',
actions.js
export function exportRecordToExcel(record) {
return ({fetch}) => ({
type: EXPORT_RECORD_TO_EXCEL,
payload: {
promise: fetch('/records/export', {
credentials: 'same-origin',
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(function(response) {
return response;
})
}
});
}
返回的响应是一个.xlsx
文件。我希望用户能够将其保存为文件,但什么也没有发生。我假设服务器返回了正确的响应类型,因为在控制台中它说
Content-Disposition:attachment; filename="report.xlsx"
我错过了什么?在reducer中应该做什么?浏览器技术目前不支持直接从Ajax请求下载文件。解决方法是添加隐藏表单并在后台提交,以使浏览器触发“保存”对话框 我正在运行一个标准的Flux实现,所以我不确定确切的Redux(Reducer)代码应该是什么,但是我刚才为文件下载创建的工作流是这样的
FileDownload
的React组件。该组件所做的只是呈现一个隐藏的表单,然后在componentDidMount
内部立即提交表单并调用它的onDownloadComplete
prop小部件
,带有一个下载按钮/图标(实际上很多…表中的每个项目对应一个)<代码>小部件具有相应的操作和存储文件<代码>小部件导入文件下载
Widget
有两种与下载相关的方法:handleDownload
和handleDownloadComplete
Widget
存储有一个名为downloadPath
的属性。默认设置为null
。当其值设置为null
时,没有正在进行的文件下载,并且小部件
组件不会呈现文件下载
组件小部件中的按钮/图标
调用handleDownload
方法,该方法触发下载文件
操作。downloadFile
操作不会发出Ajax请求。它将一个DOWNLOAD\u FILE
事件发送到商店,同时发送要下载的文件的downloadPath
。存储保存下载路径
,并发出更改事件downloadPath
,小部件将呈现FileDownload
传递必要的道具,包括downloadPath
以及handleDownloadComplete
方法作为onDownloadComplete
的值
FileDownload
并使用method=“GET”
(POST也可以)和action={downloadPath}
提交表单时,服务器响应现在将触发浏览器对目标下载文件的保存对话框(在IE 9/10、最新Firefox和Chrome中测试)onDownloadComplete
/handleDownloadComplete
。这将触发另一个操作,该操作将发送下载\u文件
事件。但是,这次downloadPath
设置为null
。存储将downloadPath
保存为null
,并发出更改事件下载路径
,文件下载
组件不会在小部件
中呈现,世界是一个快乐的地方import FileDownload from './FileDownload';
export default class Widget extends Component {
constructor(props) {
super(props);
this.state = widgetStore.getState().toJS();
}
handleDownload(data) {
widgetActions.downloadFile(data);
}
handleDownloadComplete() {
widgetActions.downloadFile();
}
render() {
const downloadPath = this.state.downloadPath;
return (
// button/icon with click bound to this.handleDownload goes here
{downloadPath &&
<FileDownload
actionPath={downloadPath}
onDownloadComplete={this.handleDownloadComplete}
/>
}
);
}
export function downloadFile(data) {
let downloadPath = null;
if (data) {
downloadPath = `${apiResource}/${data.fileName}`;
}
appDispatcher.dispatch({
actionType: actionTypes.DOWNLOAD_FILE,
downloadPath
});
}
let store = Map({
downloadPath: null,
isLoading: false,
// other store properties
});
class WidgetStore extends Store {
constructor() {
super();
this.dispatchToken = appDispatcher.register(action => {
switch (action.actionType) {
case actionTypes.DOWNLOAD_FILE:
store = store.merge({
downloadPath: action.downloadPath,
isLoading: !!action.downloadPath
});
this.emitChange();
break;
widgetStore.js-仅部分代码
import FileDownload from './FileDownload';
export default class Widget extends Component {
constructor(props) {
super(props);
this.state = widgetStore.getState().toJS();
}
handleDownload(data) {
widgetActions.downloadFile(data);
}
handleDownloadComplete() {
widgetActions.downloadFile();
}
render() {
const downloadPath = this.state.downloadPath;
return (
// button/icon with click bound to this.handleDownload goes here
{downloadPath &&
<FileDownload
actionPath={downloadPath}
onDownloadComplete={this.handleDownloadComplete}
/>
}
);
}
export function downloadFile(data) {
let downloadPath = null;
if (data) {
downloadPath = `${apiResource}/${data.fileName}`;
}
appDispatcher.dispatch({
actionType: actionTypes.DOWNLOAD_FILE,
downloadPath
});
}
let store = Map({
downloadPath: null,
isLoading: false,
// other store properties
});
class WidgetStore extends Store {
constructor() {
super();
this.dispatchToken = appDispatcher.register(action => {
switch (action.actionType) {
case actionTypes.DOWNLOAD_FILE:
store = store.merge({
downloadPath: action.downloadPath,
isLoading: !!action.downloadPath
});
this.emitChange();
break;
FileDownload.js-完整、功能齐全的代码可供复制和粘贴
-用Babel 6.x[“es2015”,“React”,“stage-0”]对0.14.7作出反应
-表单需要
display:none
,这就是“隐藏的”className
的作用
import React, {Component, PropTypes} from 'react';
import ReactDOM from 'react-dom';
function getFormInputs() {
const {queryParams} = this.props;
if (queryParams === undefined) {
return null;
}
return Object.keys(queryParams).map((name, index) => {
return (
<input
key={index}
name={name}
type="hidden"
value={queryParams[name]}
/>
);
});
}
export default class FileDownload extends Component {
static propTypes = {
actionPath: PropTypes.string.isRequired,
method: PropTypes.string,
onDownloadComplete: PropTypes.func.isRequired,
queryParams: PropTypes.object
};
static defaultProps = {
method: 'GET'
};
componentDidMount() {
ReactDOM.findDOMNode(this).submit();
this.props.onDownloadComplete();
}
render() {
const {actionPath, method} = this.props;
return (
<form
action={actionPath}
className="hidden"
method={method}
>
{getFormInputs.call(this)}
</form>
);
}
}
import React,{Component,PropTypes}来自'React';
从“react dom”导入react dom;
函数getFormInputs(){
const{queryParams}=this.props;
if(queryParams===未定义){
返回null;
}
返回Object.keys(queryParams).map((名称,索引)=>{
返回(
);
});
}
导出默认类文件下载扩展组件{
静态类型={
actionPath:PropTypes.string.isRequired,
方法:PropTypes.string,
onDownloadComplete:PropTypes.func.isRequired,
queryParams:PropTypes.object
};
静态defaultProps={
方法:“获取”
};
componentDidMount(){
ReactDOM.findDOMNode(this.submit();
this.props.ondownloadplete();
}
render(){
const{actionPath,method}=this.props;
返回(
{getFormInputs.call(this)}
);
}
}
您可以使用这两个lib下载文件
范例
// for FileSaver
import FileSaver from 'file-saver';
export function exportRecordToExcel(record) {
return ({fetch}) => ({
type: EXPORT_RECORD_TO_EXCEL,
payload: {
promise: fetch('/records/export', {
credentials: 'same-origin',
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(function(response) {
return response.blob();
}).then(function(blob) {
FileSaver.saveAs(blob, 'nameFile.zip');
})
}
});
// for download
let download = require('./download.min');
export function exportRecordToExcel(record) {
return ({fetch}) => ({
type: EXPORT_RECORD_TO_EXCEL,
payload: {
promise: fetch('/records/export', {
credentials: 'same-origin',
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(data)
}).then(function(response) {
return response.blob();
}).then(function(blob) {
download (blob);
})
}
});
我也曾经遇到过同样的问题。 我通过在空链接上创建一个引用来解决它,如下所示:
linkRef = React.createRef();
render() {
return (
<a ref={this.linkRef}/>
);
}
基本上,我已经为链接分配了blobsurl(href),设置了下载属性,并在链接上强制执行了一次单击。
据我所知,这是@Nate提供的答案的“基本”概念。
我不知道这样做是否是个好主意。。。是的。我只需要单击下载一个文件,但我需要运行一些逻辑来获取或计算文件所在的实际url。我也不想使用任何反反应命令模式,比如设置一个ref并在我有资源url时手动单击它。我使用的声明模式是
onClick = () => {
// do something to compute or go fetch
// the url we need from the server
const url = goComputeOrFetchURL();
// window.location forces the browser to prompt the user if they want to download it
window.location = url
}
render() {
return (
<Button onClick={ this.onClick } />
);
}
onClick=()=>{
//做点什么来计算或者去拿
//我们需要从服务器获取的url
const url=goComputeOrFetchURL();
//window.location强制浏览器提示用户是否要下载它
window.location=url
}
render(){
返回(
);
}
我成功地下载了rest API URL生成的文件,使用这种代码,下载起来非常简单,在我的本地计算机上运行得很好:
import React, {Component} from "react";
import {saveAs} from "file-saver";
class MyForm extends Component {
constructor(props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSubmit(event) {
event.preventDefault();
const form = event.target;
let queryParam = buildQueryParams(form.elements);
let url = 'http://localhost:8080/...whatever?' + queryParam;
fetch(url, {
method: 'GET',
headers: {
// whatever
},
})
.then(function (response) {
return response.blob();
}
)
.then(function(blob) {
saveAs(blob, "yourFilename.xlsx");
})
.catch(error => {
//whatever
})
}
render() {
return (
<form onSubmit={this.handleSubmit} id="whateverFormId">
<table>
<tbody>
<tr>
<td>
<input type="text" key="myText" name="myText" id="myText"/>
</td>
<td><input key="startDate" name="from" id="startDate" type="date"/></td>
<td><input key="endDate" name="to" id="endDate" type="date"/></td>
</tr>
<tr>
<td colSpan="3" align="right">
<button>Export</button>
</td>
</tr>
</tbody>
</table>
</form>
);
}
}
function buildQueryParams(formElements) {
let queryParam = "";
//do code here
return queryParam;
}
export default MyForm;
import React,{Component}来自“React”;
从“文件保存程序”导入{saveAs};
类MyForm扩展组件{
建造师(道具){
超级(道具);
这个,汉德尔