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

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 17:24:21  来源:igfitidea点击:

How to download fetch response in react as file

javascriptreactjsfluxreactjs-flux

提问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 .xlsxfile. 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) 代码应该是什么,但是我刚刚为文件下载创建的工作流程是这样的......

  1. I have a React component called FileDownload. All this component does is render a hidden form and then, inside componentDidMount, immediately submit the form and call it's onDownloadCompleteprop.
  2. I have another React component, we'll call it Widget, with a download button/icon (many actually... one for each item in a table). Widgethas corresponding action and store files. Widgetimports FileDownload.
  3. Widgethas two methods related to the download: handleDownloadand handleDownloadComplete.
  4. Widgetstore has a property called downloadPath. It's set to nullby default. When it's value is set to null, there is no file download in progress and the Widgetcomponent does not render the FileDownloadcomponent.
  5. Clicking the button/icon in Widgetcalls the handleDownloadmethod which triggers a downloadFileaction. The downloadFileaction does NOT make an Ajax request. It dispatches a DOWNLOAD_FILEevent to the store sending along with it the downloadPathfor the file to download. The store saves the downloadPathand emits a change event.
  6. Since there is now a downloadPath, Widgetwill render FileDownloadpassing in the necessary props including downloadPathas well as the handleDownloadCompletemethod as the value for onDownloadComplete.
  7. When FileDownloadis rendered and the form is submitted with method="GET"(POST should work too) and action={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).
  8. Immediately following the form submit, onDownloadComplete/handleDownloadCompleteis called. This triggers another action that dispatches a DOWNLOAD_FILEevent. However, this time downloadPathis set to null. The store saves the downloadPathas nulland emits a change event.
  9. Since there is no longer a downloadPaththe FileDownloadcomponent is not rendered in Widgetand the world is a happy place.
  1. 我有一个名为FileDownload. 这个组件所做的就是渲染一个隐藏的表单,然后在里面componentDidMount,立即提交表单并调用它的onDownloadCompleteprop。
  2. 我有另一个 React 组件,我们称之为Widget,带有一个下载按钮/图标(实际上很多......一个表格中的每个项目)。Widget有相应的动作和存储文件。Widget进口FileDownload
  3. Widget有两种与下载相关的方法:handleDownloadhandleDownloadComplete
  4. Widgetstore 有一个名为downloadPath. 它null默认设置为。当它的值设置为 时null,没有正在进行的文件下载并且Widget组件不会呈现FileDownload组件。
  5. 单击中的按钮/图标Widget调用handleDownload触发downloadFile操作的方法。该downloadFile操作不会发出 Ajax 请求。它DOWNLOAD_FILE向存储发送一个事件,并随它一起发送downloadPath要下载的文件。商店保存downloadPath并发出更改事件。
  6. 由于现在有downloadPath,Widget将渲染FileDownload传递必要的道具,包括downloadPath以及handleDownloadComplete方法作为 的值onDownloadComplete
  7. FileDownload呈现并提交表单时method="GET"(POST 也应该工作) and action={downloadPath},服务器响应现在将触发浏览器的目标下载文件的保存对话框(在 IE 9/10、最新的 Firefox 和 Chrome 中测试)。
  8. 紧跟在表单提交之后,onDownloadComplete/handleDownloadComplete被调用。这会触发另一个调度DOWNLOAD_FILE事件的操作。但是,该时间downloadPath设置为null。商店将其另存downloadPathnull并发出更改事件。
  9. 由于不再是downloadPathFileDownload组件没有渲染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: nonewhich is what the "hidden" classNameis 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 } />
  );
}