代码之家  ›  专栏  ›  技术社区  ›  Rafael Korbas

如何在react as文件中下载fetch响应

  •  74
  • Rafael Korbas  · 技术社区  · 10 年前

    这是中的代码 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"
    

    我错过了什么?我应该在减速器中做什么?

    7 回复  |  直到 3 年前
        1
  •  61
  •   lambda ericsoco    8 年前

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

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

    1. 我有一个名为React的组件 FileDownload 。该组件所做的只是呈现一个隐藏的表单,然后在 componentDidMount ,立即提交表格并致电 onDownloadComplete 道具
    2. 我有另一个React组件,我们称之为 Widget ,带有一个下载按钮/图标(实际上很多……表中每个项目一个)。 小装置 具有相应的操作和存储文件。 小装置 进口 文件下载 .
    3. 小装置 有两种与下载相关的方法: handleDownload handleDownloadComplete .
    4. 小装置 商店有一个名为 downloadPath 。设置为 null 默认情况下。当其值设置为 无效的 ,没有正在进行的文件下载 小装置 组件不呈现 文件下载 组成部分
    5. 单击中的按钮/图标 小装置 调用 handle下载 触发 downloadFile 行动这个 下载文件 该操作不会发出Ajax请求。它发送一个 DOWNLOAD_FILE 事件发送到商店 下载路径 以便下载文件。商店保存 下载路径 并发出改变事件。
    6. 由于现在有 下载路径 , 小装置 将渲染 文件下载 传递必要的道具,包括 下载路径 以及 handle下载完成 方法作为的值 下载完成时 .
    7. 什么时候 文件下载 呈现并提交表单 method="GET" (POST也可以)和 action={downloadPath} ,服务器响应现在将触发浏览器的目标下载文件保存对话框(在IE 9/10、最新的Firefox和Chrome中测试)。
    8. 表格提交后, 下载完成时 / handle下载完成 被调用。这将触发另一个发送 下载_文件 事件然而,这一次 下载路径 设置为 无效的 。商店保存 下载路径 无效的 并发出改变事件。
    9. 由于不再有 下载路径 这个 文件下载 组件未在中呈现 小装置 世界是一个快乐的地方。

    小装置。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}
                    />
                }
            );
        }
    

    小部件动作。js-仅部分代码

    export function downloadFile(data) {
        let downloadPath = null;
    
        if (data) {
            downloadPath = `${apiResource}/${data.fileName}`;
        }
    
        appDispatcher.dispatch({
            actionType: actionTypes.DOWNLOAD_FILE,
            downloadPath
        });
    }
    

    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;
    

    文件下载.js
    -完整、功能齐全的代码,可供复制和粘贴
    -反应0.14.7与Babel 6.x[“es2015”,“反应”,“阶段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>
            );
        }
    }
    
        2
  •  42
  •   C. S.    9 年前

    您可以使用这两个库下载文件 http://danml.com/download.html https://github.com/eligrey/FileSaver.js/#filesaverjs

    实例

    //  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);
              })
            }
          });
    
        3
  •  18
  •   Stan    7 年前

    我也面临过同样的问题。 我已经通过创建一个空链接来解决这个问题,该链接带有一个引用,如下所示:

    linkRef = React.createRef();
    render() {
        return (
            <a ref={this.linkRef}/>
        );
    }
    

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

    基本上,我已经为链接分配了blobsurl(href),设置了download属性并强制单击链接。 据我所知,这是@Nate提供的答案的“基本”想法。 我不知道这样做是不是一个好主意…我做了。

        4
  •  13
  •   Community Mohan Dere    4 年前

    这对我有用。

    const requestOptions = {
    method: 'GET',
    headers: { 'Content-Type': 'application/json' }
    };
    
    fetch(`${url}`, requestOptions)
    .then((res) => {
        return res.blob();
    })
    .then((blob) => {
        const href = window.URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = href;
        link.setAttribute('download', 'config.json'); //or any other extension
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
    })
    .catch((err) => {
        return Promise.reject({ Error: 'Something Went Wrong', err });
    })
    
        5
  •  4
  •   Raluca Popa    5 年前

    我成功地下载了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;
    
        6
  •  2
  •   Jose    6 年前

    我只需要在Click上下载一个文件,但我需要运行一些逻辑来获取或计算文件所在的实际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 } />
      );
    }
    
        7
  •  0
  •   NSjonas    3 年前

    我认为这个解决方案可能比其他解决方案更“被动”:

    import React, { forwardRef, useImperativeHandle, useLayoutEffect, useState } from 'react';
    
    export interface DownloadHandle {
      download: (params: { title: string; data?: Blob }) => void;
    }
    
    export const Download = forwardRef<DownloadHandle, {}>((props, ref) => {
      const linkRef = React.useRef<HTMLAnchorElement>(null);
      const [download, setDownload] = useState<{ title: string; data: Blob }>();
      useImperativeHandle(ref, () => ({
        download: (params) => {
          if (params.data) {
            setDownload(params as typeof download);
          }
        },
      }));
    
      //trigger download and clear data
      useLayoutEffect(() => {
        if (download) {
          linkRef?.current?.click();
        }
        setDownload(undefined);
      }, [download]);
    
      if (!download) {
        return null;
      }
      const { title, data } = download;
      return <a href={window.URL.createObjectURL(data)} download={title} ref={linkRef} />;
    });
    
    export type DownloadElement = React.ElementRef<typeof Download>;
    

    用法

    const App = () => {
    
      const downloadRef = useRef<DownloadElement>(null);
    
      const handleDownload = () => {
        fetch(url, requestOptions)
        .then((res) => res.blob())
        .then((data) => {
          downloadRef.current?.download({ title: `myFile.txt`, data});
        });
      }
    
      return (
        <div>
          <Download ref={downloadRef} />
          <button onClick={}>Download</button>
        </div>
      )
    }
    
    推荐文章