如何在反应中下载获取响应作为文件



这是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"

我错过了什么?在减速机里我应该做什么?

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

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

  1. 我有一个名为 FileDownload 的 React 组件。这个组件所做的只是渲染一个隐藏的表单,然后在componentDidMount中,立即提交表单并调用它的onDownloadComplete prop。
  2. 我还有另一个 React 组件,我们称之为 Widget ,带有下载按钮/图标(实际上很多......表中每个项目一个(。 Widget具有相应的操作和存储文件。 Widget进口FileDownload .
  3. Widget有两种与下载相关的方法:handleDownloadhandleDownloadComplete
  4. Widget商店有一个名为 downloadPath 的属性。默认情况下,它设置为 null。当它的值设置为 null 时,没有正在进行的文件下载,并且Widget组件不会呈现FileDownload组件。
  5. 单击Widget中的按钮/图标将调用触发downloadFile操作的 handleDownload 方法。downloadFile操作不会发出 Ajax 请求。它将DOWNLOAD_FILE事件调度到存储区,并随之发送文件下载downloadPath。存储保存downloadPath并发出更改事件。
  6. 由于现在有一个downloadPathWidget将渲染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渲染,世界是一个快乐的地方。

小部件.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 - 仅部分代码

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
- 完整、功能齐全的代码,可复制和粘贴
- 使用 Babel 6.x 的 React 0.14.7 ["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>
        );
    }
}
您可以使用

这两个库下载文件 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);
          })
        }
      });

我也曾经遇到过同样的问题。我已经通过在空链接上创建带有引用的引用来解决它,如下所示:

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

在我的获取函数中,我做了这样的事情:

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

基本上,我已经将 blob URL(HREF( 分配给链接,设置下载属性并强制一键单击链接。据我了解,这是@Nate提供的答案的"基本"思想。我不知道这样做是否是个好主意......我做了。

这对

我有用。

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

我设法使用这种代码更轻松地下载由其余 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;

我只需要在单击上下载一个文件,但我需要运行一些逻辑来获取或计算文件所在的实际网址。我也不想使用任何反反应命令式模式,例如设置 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 } />
  );
}

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

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

最新更新