Javascript 使用React和Axios从Express API下载文件

Javascript 使用React和Axios从Express API下载文件,javascript,reactjs,express,axios,Javascript,Reactjs,Express,Axios,使用带有Express API的React客户端时,React客户端如何下载Express API发送的文件? 问题: 如果我在浏览器栏中键入url并按enter键,则文件下载成功 但如果我使用Axios在React应用程序中调用相同的url,则不会下载该文件 Express server //用于/api/files/testfile的路由处理程序 const getFile=async(请求、恢复、下一步)=>{ //文件 常量文件名='file.csv'; const filePath

使用带有Express API的React客户端时,React客户端如何下载Express API发送的文件?

问题:

  • 如果我在浏览器栏中键入url并按enter键,则文件下载成功
  • 但如果我使用Axios在React应用程序中调用相同的url,则不会下载该文件
Express server

//用于/api/files/testfile的路由处理程序
const getFile=async(请求、恢复、下一步)=>{
//文件
常量文件名='file.csv';
const filePath=path.join(_dirname,'/../../public/',fileName);
//文件选项
常量选项={
标题:{
“x-timestamp”:Date.now(),
“x-sent”:正确,
“内容处置”:“附件;文件名=“+filename,//被忽略
“内容类型”:“文本/csv”
}
}
试一试{
res.下载(
文件路径,
文件名,
选择权
);
log(“文件发送成功!”);
}
捕获(错误){
console.error(“无法发送文件!”);
下一步(错误);
}
});
反应客户端

//当用户单击“下载为CSV”按钮时
handleDownloadFile=()=>{
axios
.得到(
`/api/files/testfile`{
responseType:'blob',
标题:{
“内容类型”:“文本/csv”,
}
}
)
。然后(响应=>{
console.log(response.headers);//不包括内容处置
log(“文件下载成功!”);
})
.catch((错误)=>{
console.error(“无法下载文件:”,错误);
});
}
我了解到这可能与
内容处置
标题有关。我尝试在中进行设置(请参阅上面的代码),但标题没有发送到客户端


不受欢迎的“解决方案”:

  • 在React应用程序中:创建一个新的
    a
    元素,设置它的
    href
    属性,并通过JavaScript触发一个
    单击
    。我正在寻找一个解决方案,不需要这个JS黑客

  • 在React应用程序中:使用
    a
    target=“\u blank”
    代替Axios。但是,这不适合我,因为它会绕过我的axios配置设置(API url、身份验证令牌等)


不幸的是,目前还没有可靠的跨平台方法可以触发正常网页的浏览器下载行为。由于您不能在普通DOM锚标记上使用带有内容配置、重定向或数据URI的普通URL,因此我看不到其他方法可以在不创建隐藏的
a
并单击它的情况下进行下载。然而,这似乎工作得很好(这确实是流行的实用程序使用的机制,例如)

在React中构建一个粗糙的
DownloadButton
组件来实现这一点非常简单。这会模拟Axios响应,否则会从头到尾工作,除非您愿意进行任何重构。我使用hook和
async/await
是为了我自己的理智/清晰,但两者都不是绝对必要的。它确实使用了on-anchor标记,这在现代浏览器中有很好的支持

function getFileNameFromContentDisposition(contentDisposition) {
  if (!contentDisposition) return null;

  const match = contentDisposition.match(/filename="?([^"]+)"?/);

  return match ? match[1] : null;
}

const DownloadButton = ({ children, fileName, loadingText }) => {
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState(null);

  const handleClick = async () => {
    setLoading(true);
    setError(null);

    let res = null;

    try {
      // add any additional headers, such as authorization, as the second parameter to get below
      // also, remember to use responseType: 'blob' if working with blobs instead, and use res.blob() instead of res.data below
      res = await axios.get(`/api/files/${fileName}`);
      setLoading(false);
    } catch (err) {
      setLoading(false);
      setError(err);
      return;
    }

    const data = res.data; // or res.blob() if using blob responses

    const url = window.URL.createObjectURL(
      new Blob([data], {
        type: res.headers["content-type"]
      })
    );

    const actualFileName = getFileNameFromContentDisposition(
      res.headers["content-disposition"]
    );

    // uses the download attribute on a temporary anchor to trigger the browser
    // download behavior. if you need wider compatibility, you can replace this
    // part with a library such as filesaver.js
    const link = document.createElement("a");
    link.href = url;
    link.setAttribute("download", actualFileName);
    document.body.appendChild(link);
    link.click();
    link.parentNode.removeChild(link);
  };

  if (error) {
    return (<div>Unable to download file: {error.message}</div>);
  }

  return (
    <button onClick={handleClick} disabled={loading}>
      {loading ? loadingText || "Please wait..." : children}
    </button>
  );
};
函数getFileNameFromContentDisposition(contentDisposition){ 如果(!contentDisposition)返回null; const match=contentDisposition.match(/filename=“?([^”]+)”?/); 返回匹配?匹配[1]:空; } const DownloadButton=({children,fileName,loadingText})=>{ 常量[loading,setLoading]=React.useState(false); const[error,setError]=React.useState(null); const handleClick=async()=>{ 设置加载(真); 设置错误(空); 设res=null; 试一试{ //添加任何附加的头,例如authorization,作为下面的第二个参数 //另外,如果使用blob,请记住使用responseType:'blob',并使用res.blob()代替下面的res.data res=wait axios.get(`/api/files/${fileName}`); 设置加载(假); }捕捉(错误){ 设置加载(假); 设置错误(err); 返回; } const data=res.data;//或res.blob(),如果使用blob响应 常量url=window.url.createObjectURL( 新Blob([数据]{ 类型:res.headers[“内容类型”] }) ); const actualFileName=getFileNameFromContentDisposition( res.headers[“内容处置”] ); //使用临时定位上的“下载”属性触发浏览器 //下载行为。如果您需要更广泛的兼容性,可以替换此 //使用诸如filesaver.js之类的库 const link=document.createElement(“a”); link.href=url; setAttribute(“下载”,实际文件名); document.body.appendChild(链接); link.click(); link.parentNode.removeChild(link); }; 如果(错误){ 返回(无法下载文件:{error.message}); } 返回( {加载?加载文本| |“请稍候…”:儿童} ); }; 至于ExpressJS的响应标题中未显示的
内容处置
,我不确定问题出在哪里。但是,根据,第二个参数是文件名,它将自动作为
内容处置
标题发送,因此您不必在
选项
段落中自行指定meter.是否显示其他参数?如果是,可能在重新定义它时存在冲突
选项
。但是,在本地运行与您的路由类似的示例时,我没有遇到任何问题

res.download(路径[,文件名][,选项][,fn])

Express v4.16.0及以后版本支持可选选项参数

以“附件”的形式在路径处传输文件。通常,浏览器 将提示美国
axios({
  url: 'http://localhost:5000/static/example.pdf',
  method: 'GET',
  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', 'file.pdf');
  document.body.appendChild(link);
  link.click();
});
npm install --save js-file-download
 import download from 'js-file-download';
 downloadFile = () => {
   axios.get("localhost:3000/route/path/url")
     .then(resp => {
            download(resp.data, fileName);
     });
}