如何下载fetch响应作为文件 [英] How to download fetch response in react as file

查看:93
本文介绍了如何下载fetch响应作为文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下是 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 文件。我希望用户能够将其保存为文件,但没有任何反应。我假设服务器正在返回正确类型的响应,因为在控制台中它说

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

Content-Disposition:attachment; filename="report.xlsx"

我错过了什么?我应该在reducer中做什么?

What I'm I missing? What should I do in the reducer?

推荐答案

浏览器技术目前不支持直接从Ajax请求下载文件。解决方法是添加隐藏的表单并在幕后提交,以使浏览器触发保存对话框。

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.

我正在运行标准的Flux实现,所以我我不确定Redux(Reducer)代码应该是什么,但我刚为文件下载创建的工作流程就像这样...

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...


  1. 我有一个名为 FileDownload 的React组件。所有这个组件都会呈现一个隐藏的表单,然后在 componentDidMount 内,立即提交表单并调用它的 onDownloadComplete prop 。

  2. 我有另一个React组件,我们称之为 Widget ,带有下载按钮/图标(实际上很多......一个表中的每个项目)。 小部件具有相应的操作和存储文件。 小部件导入 FileDownload

  3. Widget 有两种与下载相关的方法: handleDownload handleDownloadComplete

  4. Widget store有一个名为 downloadPath 的属性。默认情况下,它设置为 null 。当它的值设置为 null 时,没有正在进行的文件下载,并且 Widget 组件不会呈现 FileDownload 组件。

  5. 点击 Widget 中的按钮/图标调用 handleDownload 触发 downloadFile 操作的方法。 downloadFile 操作不会发出Ajax请求。它将 DOWNLOAD_FILE 事件发送到商店,并随之发送 downloadPath 以供下载文件。商店保存 downloadPath 并发出更改事件。

  6. 因为现在有 downloadPath 小部件将呈现 FileDownload 传递必要的道具,包括 downloadPath 以及 handleDownloadComplete 方法作为 onDownloadComplete 的值。

  7. 当呈现 FileDownload 时,表单以 method =GET提交(POST也应该有效) )和 action = {downloadPath} ,服务器响应现在将触发浏览器的目标下载文件的保存对话框(在IE 9/10中测试,最新的Firefox和Chrome)。

  8. 在表单提交后,立即调用 onDownloadComplete / handleDownloadComplete 。这会触发另一个调度 DOWNLOAD_FILE 事件的操作。但是,这次 downloadPath 设置为 null 。商店将 downloadPath 保存为 null 并发出更改事件。

  9. 由于不再有 downloadPath FileDownload 组件未在 Widget中呈现世界是个幸福的地方。

  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 onDownloadComplete prop.
  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). Widget has corresponding action and store files. Widget imports FileDownload.
  3. Widget has two methods related to the download: handleDownload and handleDownloadComplete.
  4. Widget store has a property called downloadPath. It's set to null by default. When it's value is set to null, there is no file download in progress and the Widget component does not render the FileDownload component.
  5. Clicking the button/icon in Widget calls the handleDownload method which triggers a downloadFile action. The downloadFile action does NOT make an Ajax request. It dispatches a DOWNLOAD_FILE event to the store sending along with it the downloadPath for the file to download. The store saves the downloadPath and emits a change event.
  6. Since there is now a downloadPath, Widget will render FileDownload passing in the necessary props including downloadPath as well as the handleDownloadComplete method as the value for onDownloadComplete.
  7. When FileDownload is 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/handleDownloadComplete is called. This triggers another action that dispatches a DOWNLOAD_FILE event. However, this time downloadPath is set to null. The store saves the downloadPath as null and emits a change event.
  9. Since there is no longer a downloadPath the FileDownload component is not rendered in Widget and the world is a happy place.

Widget.js - 仅限部分代码

Widget.js - partial code only

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 - 仅限部分代码

widgetActions.js - partial code only

export function downloadFile(data) {
    let downloadPath = null;

    if (data) {
        downloadPath = `${apiResource}/${data.fileName}`;
    }

    appDispatcher.dispatch({
        actionType: actionTypes.DOWNLOAD_FILE,
        downloadPath
    });
}

widgetStore.js - 仅限部分代码

widgetStore.js - partial code only

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反应0.14.7 [es2015,反应,阶段0]

- 表格需要 display:none 这是隐藏的 className 用于

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

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>
        );
    }
}

这篇关于如何下载fetch响应作为文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆