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
  • 我有另一个React组件,我们称之为
    小部件
    ,带有一个下载按钮/图标(实际上很多…表中的每个项目对应一个)<代码>小部件具有相应的操作和存储文件<代码>小部件导入
    文件下载
  • 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
    ,并发出更改事件
  • 由于不再有
    下载路径
    文件下载
    组件不会在
    小部件
    中呈现,世界是一个快乐的地方
  • 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}
                    />
                }
            );
        }
    
    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扩展组件{
    建造师(道具){
    超级(道具);
    这个,汉德尔