如何在React JS中下载文件

216

我从API接收文件的URL作为响应。当用户点击下载按钮时,应该直接下载文件而不是在新标签页中打开文件预览。如何在React.js中实现?


4
从前端触发浏览器下载不是一种可靠的方式。您应该创建一个终端,当调用时,可以提供正确的响应标头,从而触发浏览器下载。前端代码只能做这么多。例如,“download”属性可能会根据浏览器,仅在新标签页中打开文件。 - Jackyef
1
根据我的理解,您是说可以通过正确的响应头使用REST API来实现,对吗? - Sameer Thite
1
是的。我不知道如何在评论中添加链接,所以我发了一个回答。 - Jackyef
24个回答

125

tldr; 从URL获取文件,将其存储为本地Blob,在DOM中注入链接元素并单击它以下载Blob。

我有一个PDF文件,它存储在Cloudfront URL后面的S3中。我希望用户能够单击按钮并立即启动下载,而不会弹出具有PDF预览的新标签页。通常,如果文件托管在与用户当前所在网站不同的URL上,则许多浏览器出于用户安全原因会阻止立即下载。如果使用此解决方案,请不要在没有用户点击按钮有意下载的情况下启动文件下载。

为了绕过此问题,我需要从URL获取文件,避开任何CORS策略来保存本地Blob,然后该Blob将是下载文件的源。在下面的代码中,请确保您替换自己的fileURLContent-TypeFileName

fetch('https://cors-anywhere.herokuapp.com/' + fileURL, {
    method: 'GET',
    headers: {
      'Content-Type': 'application/pdf',
    },
  })
  .then((response) => response.blob())
  .then((blob) => {
    // Create blob link to download
    const url = window.URL.createObjectURL(
      new Blob([blob]),
    );
    const link = document.createElement('a');
    link.href = url;
    link.setAttribute(
      'download',
      `FileName.pdf`,
    );

    // Append to html link element page
    document.body.appendChild(link);

    // Start download
    link.click();

    // Clean up and remove the link
    link.parentNode.removeChild(link);
  });

这个解决方案涉及到如何从URL获取blob和使用CORS代理的解决方案的引用。getting a blob from a URLusing a CORS proxy.

更新截至2021年1月31日,托管在Heroku服务器上的cors-anywhere演示只允许有限的用于测试目的的使用,并且不能用于生产应用程序。您需要按照cors-anywherecors-server的指示自己托管cors-anywhere服务器。


感谢@Brian Li的分享,非常好的文章。我有一个问题,你是如何管理内存的呢?当使用createObjectURL时,通常会使用revokeObjectURL来从内存中删除对象。我认为,如果不这样做,可能会在重复下载大文件时出现问题。当然,我的想法可能有误。 - NAJ
2
从js-file-download的代码中可以看出,他们使用了一个短定时器。这是我现在根据你的代码实现的。感谢你的帖子。 setTimeout(function () { document.body.removeChild(link); window.URL.revokeObjectURL(url); }, 200); - NAJ
2
为什么要使用 new Blob([blob])?你已经有一个 blob 中的 Blob 了。 - Phil

98

3
锚标签在同一浏览器选项卡中打开图像(图像预览),然后我们需要手动保存图像。我正在尝试通过按钮单击实现自动下载,而不是打开文件预览。 - Sameer Thite
请参考以下链接获取更多详细信息:https://dev59.com/9HE95IYBdhLWcg3wN7Kv - lipp
6
“download” 遵循同源策略,供您参考。 - user
5
2021年更新:download 属性在95%的浏览器中得到支持:https://caniuse.com/download,因此... ‍♂️ - fullStackChris

83
如果您正在使用React Router,请使用以下内容:
<Link to="/files/myfile.pdf" target="_blank" download>Download</Link>

/files/myfile.pdf 存在于您的 public 文件夹中时。


我尝试下载zip文件,文件下载了,但是无法正确解压缩。有人知道原因吗?顺便说一下,我找到了一个解决方法:https://dev59.com/jLXna4cB1Zd3GeqPGChT#62855917 - Rohan Kumar
1
@Javier López 如果这是API响应中的链接,但我想使用自定义文件名而不是服务器生成的文件名,该怎么办?我要使用React Router。 - Compaq LE2202x

76

在前端触发浏览器下载不可靠。

你应该在服务器上创建一个端点,当调用它时,它将响应正确的响应头,从而触发浏览器下载。

前端代码只能做这么多。例如,'download'属性可能会根据浏览器和文件类型打开新标签页。

你需要查看的响应头是Content-TypeContent-Disposition。你应该参考这个答案来获取更详细的解释。


10
你需要做的是创建一个端点,当被调用时,会提供正确的响应头,从而触发浏览器下载...。这些响应头是什么,特定的响应头如何触发浏览器下载?谢谢。 - 1020rpz
抱歉回复晚了。所涉及的标题是“Content-Type”和“Content-Disposition”。 - Jackyef

49

解决方法(适用于React JS、Next JS)

你可以使用js-file-download,这是我的示例:

import axios from 'axios'
import fileDownload from 'js-file-download'
 
...

handleDownload = (url, filename) => {
  axios.get(url, {
    responseType: 'blob',
  })
  .then((res) => {
    fileDownload(res.data, filename)
  })
}
 
...

<button onClick={() => {this.handleDownload('https://your-website.com/your-image.jpg', 'test-download.jpg')
}}>Download Image</button>

这个插件可以下载Excel和其他文件类型。


1
这个工作得很好,要求保存为,这正是我想要的。 - Santhosh
2
我无法在下载后打开文件,文件错误:“无法打开文件(nameFile)。它可能已损坏或使用了预览不识别的文件格式。” 我有什么遗漏吗? - hoanghuychh
1
它对我有效。谢谢你提供的解决方案。 - MK Patel
@hoanghuychh,可能是因为您提供的路径未能找到文件。我曾经多次遇到这个问题,当提供相对路径而不是绝对路径时,就会频繁发生这种情况(因为下载操作开始的当前URL可能与文件所在的基本路径不匹配)。 - spuente

40

浏览器足够智能,可以检测到链接并在单击锚标签时直接下载,而无需使用下载属性。

从 API 获取文件链接后,只需使用纯 JavaScript 创建锚标签,并在动态单击后立即删除它。

const link = document.createElement('a');
link.href = `your_link.pdf`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);

3
除了浏览器会打开PDF文件,我想让它下载而非打开。 - Jake
它也可以与React和Node/HAPI一起使用。我已经添加了以下链接。href = your_link.pdf; link.download = yourFileName; 非常感谢。 - Pankaj

32

这是我在React中的实现方式:

import MyPDF from '../path/to/file.pdf';
<a href={myPDF} download="My_File.pdf"> Download Here </a>

重要的是使用download="name_of_file_you_want.pdf"覆盖默认文件名,否则在下载时文件名称将添加散列号。


18

当使用a标签并设置target="_blank"时,React会存在安全问题。

我成功地让它像这样工作:

<a href={uploadedFileLink} target="_blank" rel="noopener noreferrer" download>
   <Button>
      <i className="fas fa-download"/>
      Download File
   </Button>
</a>

11

从'../assets/data/resume.pdf'导入简历

<a href={resume} download="YourName resume.pdf"> Download CV </a>

我能够以相同的方式添加一个epub文件 - fatemeh kazemi
非常感谢,这在React中起作用了 (y) - Adil Saju

11
fetchFile(){
  axios({
        url: `/someurl/thefiles/${this.props.file.id}`,
        method: "GET",
        headers: headers,
        responseType: "blob" // important
    }).then(response => {
        const url = window.URL.createObjectURL(new Blob([response.data]));
        const link = document.createElement("a");
        link.href = url;
        link.setAttribute(
            "download",
            `${this.props.file.name}.${this.props.file.mime}`
        );
        document.body.appendChild(link);
        link.click();

        // Clean up and remove the link
        link.parentNode.removeChild(link);
    });
}
render(){
  return( <button onClick={this.fetchFile}> Download file </button>)
}

6
请注意,如果在您的应用程序中未重新加载页面,则传递给“URL.createObjectURL”的Blob将一直存在于内存中,直到调用URL.revokeObjectUrl。对于长时间运行的应用程序,这可能会导致浪费内存和性能问题。 - Nils Knappmeier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接