Javascript 如何在反应为文件中下载获取响应
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/35206589/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to download fetch response in react as file
提问by Rafael K.
Here is the code in actions.js
这里是代码 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;
})
}
});
}
The returned response is an .xlsx
file. I want the user to be able to save it as a file, but nothing happens. I assume the server is returning the right type of response because in the console it says
返回的响应是一个.xlsx
文件。我希望用户能够将其保存为文件,但没有任何反应。我假设服务器正在返回正确类型的响应,因为在控制台中它说
Content-Disposition:attachment; filename="report.xlsx"
What I'm I missing? What should I do in the reducer?
我错过了什么?减速机应该怎么做?
回答by Nate
Browser technology currently doesn't support downloading a file directly from an Ajax request. The work around is to add a hidden form and submit it behind the scenes to get the browser to trigger the Save dialog.
浏览器技术目前不支持直接从 Ajax 请求下载文件。解决方法是添加一个隐藏的表单并在后台提交它以使浏览器触发“保存”对话框。
I'm running a standard Flux implementation so I'm not sure what the exact Redux (Reducer) code should be, but the workflow I just created for a file download goes like this...
我正在运行一个标准的 Flux 实现,所以我不确定确切的 Redux (Reducer) 代码应该是什么,但是我刚刚为文件下载创建的工作流程是这样的......
- I have a React component called
FileDownload
. All this component does is render a hidden form and then, insidecomponentDidMount
, immediately submit the form and call it'sonDownloadComplete
prop. - I have another React component, we'll call it
Widget
, with a download button/icon (many actually... one for each item in a table).Widget
has corresponding action and store files.Widget
importsFileDownload
. Widget
has two methods related to the download:handleDownload
andhandleDownloadComplete
.Widget
store has a property calleddownloadPath
. It's set tonull
by default. When it's value is set tonull
, there is no file download in progress and theWidget
component does not render theFileDownload
component.- Clicking the button/icon in
Widget
calls thehandleDownload
method which triggers adownloadFile
action. ThedownloadFile
action does NOT make an Ajax request. It dispatches aDOWNLOAD_FILE
event to the store sending along with it thedownloadPath
for the file to download. The store saves thedownloadPath
and emits a change event. - Since there is now a
downloadPath
,Widget
will renderFileDownload
passing in the necessary props includingdownloadPath
as well as thehandleDownloadComplete
method as the value foronDownloadComplete
. - When
FileDownload
is rendered and the form is submitted withmethod="GET"
(POST should work too) andaction={downloadPath}
, the server response will now trigger the browser's Save dialog for the target download file (tested in IE 9/10, latest Firefox and Chrome). - Immediately following the form submit,
onDownloadComplete
/handleDownloadComplete
is called. This triggers another action that dispatches aDOWNLOAD_FILE
event. However, this timedownloadPath
is set tonull
. The store saves thedownloadPath
asnull
and emits a change event. - Since there is no longer a
downloadPath
theFileDownload
component is not rendered inWidget
and the world is a happy place.
- 我有一个名为
FileDownload
. 这个组件所做的就是渲染一个隐藏的表单,然后在里面componentDidMount
,立即提交表单并调用它的onDownloadComplete
prop。 - 我有另一个 React 组件,我们称之为
Widget
,带有一个下载按钮/图标(实际上很多......一个表格中的每个项目)。Widget
有相应的动作和存储文件。Widget
进口FileDownload
。 Widget
有两种与下载相关的方法:handleDownload
和handleDownloadComplete
。Widget
store 有一个名为downloadPath
. 它null
默认设置为。当它的值设置为 时null
,没有正在进行的文件下载并且Widget
组件不会呈现FileDownload
组件。- 单击中的按钮/图标
Widget
调用handleDownload
触发downloadFile
操作的方法。该downloadFile
操作不会发出 Ajax 请求。它DOWNLOAD_FILE
向存储发送一个事件,并随它一起发送downloadPath
要下载的文件。商店保存downloadPath
并发出更改事件。 - 由于现在有
downloadPath
,Widget
将渲染FileDownload
传递必要的道具,包括downloadPath
以及handleDownloadComplete
方法作为 的值onDownloadComplete
。 - 当
FileDownload
呈现并提交表单时method="GET"
(POST 也应该工作) andaction={downloadPath}
,服务器响应现在将触发浏览器的目标下载文件的保存对话框(在 IE 9/10、最新的 Firefox 和 Chrome 中测试)。 - 紧跟在表单提交之后,
onDownloadComplete
/handleDownloadComplete
被调用。这会触发另一个调度DOWNLOAD_FILE
事件的操作。但是,该时间downloadPath
设置为null
。商店将其另存downloadPath
为null
并发出更改事件。 - 由于不再是
downloadPath
该FileDownload
组件没有渲染Widget
和世界是一个快乐的地方。
Widget.js - partial code only
Widget.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}
/>
}
);
}
widgetActions.js - partial code only
widgetActions.js - 仅部分代码
export function downloadFile(data) {
let downloadPath = null;
if (data) {
downloadPath = `${apiResource}/${data.fileName}`;
}
appDispatcher.dispatch({
actionType: actionTypes.DOWNLOAD_FILE,
downloadPath
});
}
widgetStore.js - partial code only
widgetStore.js - 仅部分代码
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
- complete, fully functional code ready for copy and paste
- React 0.14.7 with Babel 6.x ["es2015", "react", "stage-0"]
- form needs to be display: none
which is what the "hidden" className
is for
FileDownload.js
- 准备好复制和粘贴的完整、功能齐全的代码
- React 0.14.7 with Babel 6.x ["es2015", "react", "stage-0"]
- 表单需要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>
);
}
}
回答by Anton Philin
You can use these two libs to download files http://danml.com/download.htmlhttps://github.com/eligrey/FileSaver.js/#filesaverjs
您可以使用这两个库下载文件http://danml.com/download.html https://github.com/eligrey/FileSaver.js/#filesaverjs
example
例子
// 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);
})
}
});
回答by Stanislav
I have faced the same problem once too. I have solved it by creating on empty link with a ref to it like so:
我也遇到过同样的问题。我通过在空链接上创建一个引用来解决它,如下所示:
linkRef = React.createRef();
render() {
return (
<a ref={this.linkRef}/>
);
}
and in my fetch function i have done something like this:
在我的 fetch 函数中,我做了这样的事情:
fetch(/*your params*/)
}).then(res => {
return res.blob();
}).then(blob => {
const href = window.URL.createObjectURL(blob);
const a = this.linkRef.current;
a.download = 'Lebenslauf.pdf';
a.href = href;
a.click();
a.href = '';
}).catch(err => console.error(err));
basically i have assigned the blobs url(href) to the link, set the download attribute and enforce one click on the link. As far as i understand this is the "basic" idea of the answer provided by @Nate. I dont know if this is a good idea to do it this way... I did.
基本上,我已将 blob url(href) 分配给链接,设置下载属性并强制单击链接。据我所知,这是@Nate 提供的答案的“基本”想法。我不知道这样做是否是个好主意……我做到了。
回答by Jose
I needed to just download a file onClick but I needed to run some logic to either fetch or compute the actual url where the file existed. I also did not want to use any anti-react imperative patterns like setting a ref and manually clicking it when I had the resource url. The declarative pattern I used was
我只需要下载一个文件 onClick 但我需要运行一些逻辑来获取或计算文件所在的实际 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 } />
);
}