programing

응답 다운로드JS 개체(파일)

subpage 2023. 8. 22. 22:09
반응형

응답 다운로드JS 개체(파일)

저는 Express API 서버에 연결되는 ReactJS 프런트 엔드로 애플리케이션을 구축하고 있습니다.API에 대한 호출은 Ajax를 사용하여 이루어집니다.

내 보기 중 하나에서 테이블은 각 행에 "내보내기" 링크와 함께 로드됩니다.Export 링크는 다운로드할 CSV 파일을 제공하는 API 끝점을 호출하는 React 경로로 이어집니다.

유효한 요청(React 앱 외부)으로 API 끝점을 직접 누르면 브라우저에서 파일 다운로드가 시작됩니다.완벽해!그러나 React 페이지에서 Export 링크를 클릭하면 API 호출이 발생하는 보기를 로드하려고 합니다.테이블이 보기에서 사라지고 파일 내용으로 대체되지만(데이터가 있다는 것을 증명하기 위해 의도적으로) 파일은 다운로드되지 않습니다.

응답 개체의 내용을 강제로 파일로 다운로드할 수 있습니까?이것이 아약스 성공 콜백에서 발생할 수 있습니까?Javascript로 시도해봤는데 React virtual DOM 때문에 어려움을 겪고 있습니다...나는 이것이 꽤 솔직할 것이라고 생각하지만 나는 당황스럽습니다.

편집: @Blex의 의견이 이 문제를 해결하는 데 도움이 되었습니다!솔루션이 코드 조각에 추가되었습니다...

다음은 데이터를 수신하는 JSX입니다.

module.exports = React.createClass({

    mixins: [Router.State],
    getInitialState: function() {
        return {
            auth: getAuthState(),
            export: [],
            passedParams: this.getParams()
        };
    },

    componentDidMount: function(){
        $.ajax({
            type: 'GET',
            url: ''+ API_URL +'/path/to/endpoint'+ this.state.passedParams.id +'/export',
            dataType: 'text',
            headers: {
                'Authorization': 'Basic ' + this.state.auth.base + ''
            },
            success: function (res) {
                // can I force a download of res here?
                console.log('Export Result Success -- ', res);
                if(this.isMounted()){
                    console.log('Export Download Data -- ', res);
                    this.setState({export: res[1]});
                    // adding the next three lines solved my problem
                    var data = new Blob([res], {type: 'text/csv'});
                    var csvURL = window.URL.createObjectURL(data);
                    //window.open(csvURL);
                    // then commenting out the window.open & replacing
                    // with this allowed a file name to be passed out
                    tempLink = document.createElement('a');
                    tempLink.href = csvURL;
                    tempLink.setAttribute('download', 'filename.csv');
                    tempLink.click();
                }
            }.bind(this),
            error: function (data) {
                console.log('Export Download Result Error -- ', data);
            }
        });
    },

    render: function(){
        console.log('exam assignment obj -- ', this.state.passedParams.name);
        var theFileContents = this.state.export;
            return(
            <div className="row test-table">
                <table className="table" >
                    <tr className="test-table-headers">
                    {theFileContents} // this loads the contents
                    // can I auto download theFileContents?
                    </tr>
                </table>
            </div>
            )
    }
});

@blex의 코멘트를 기반으로 다음 코드를 추가하면 파일 다운로드가 작동합니다.맥락을 파악하려면 질문의 성공적인 콜백을 살펴봅니다.

var data = new Blob([res], {type: 'text/csv'});
var csvURL = window.URL.createObjectURL(data);
tempLink = document.createElement('a');
tempLink.href = csvURL;
tempLink.setAttribute('download', 'filename.csv');
tempLink.click();

나는 리액트 앱에서 패키지 jsonexport를 사용했고 이제 링크 클릭으로 csv 파일을 다운로드할 수 있습니다.제가 한 일은 다음과 같습니다.

.
.
import React, {useState,useEffect} from 'react';// I am using React Hooks
import * as jsonexport from "jsonexport/dist";
.
.
.
const [filedownloadlink, setFiledownloadlink] = useState("");//To store the file download link

.
.
.

CSV에 대한 데이터를 제공하는 함수를 만듭니다.네트워크 요청의 콜백에 있을 수도 있습니다.이 메서드를 호출하면 다음 값이 설정됩니다.filedownloadlink주.

function handleSomeEvent(){
var contacts = [{
        name: 'Bob',
        lastname: 'Smith'
    },{
        name: 'James',
        lastname: 'David'
    },{
        name: 'Robert',
        lastname: 'Miller' 
    },{
        name: 'David',
        lastname: 'Martin'
    }];

    jsonexport(contacts,function(err, csv){
        if(err) return console.log(err);
        var myURL = window.URL || window.webkitURL //window.webkitURL works in Chrome and window.URL works in Firefox
        var csv = csv;  
        var blob = new Blob([csv], { type: 'text/csv' });  
        var csvUrl = myURL.createObjectURL(blob);
        setFiledownloadlink(csvUrl);
    });
}

렌더 함수에서 다음과 같은 것을 사용합니다.

{filedownloadlink &&<a download="UserExport.csv" href={filedownloadlink}>Download</a>}

다음과 같은 경우 위 링크를 볼 수 있습니다.filedownloadlink다운로드할 데이터가 있습니다.

나중에 참조할 수 있도록 아래 코드를 추가합니다.여기에는 브라우저 호환성에 대한 몇 가지 추가 검사와 IE10+를 포함하는 추가 코드가 포함됩니다.

/**
 * Take a blob and force browser to click a link and save it from a download path
 * log out timing
 *
 * @param {Blob}
 * @method saveFile
 */
function saveFile(blob) {
    const uniqTime = new Date().getTime();
    const filename = `my_file_${uniqTime}`;

    if (navigator.msSaveBlob) { // IE 10+
        console.info('Starting call for ' + 'ie download');
        const csvFormatTimeStart = new Date().getTime();

        const ieFilename = `${filename}.csv`;
        navigator.msSaveBlob(blob, ieFilename);

        const csvFormatTimeEnd = new Date().getTime();
        const csvFormatTime = csvFormatTimeEnd - csvFormatTimeStart;
        console.log('ie download takes ' + csvFormatTime + ' ms to run');
    } else {
        console.info('Starting call for ' + 'regular download');
        const csvFormatTimeStart = new Date().getTime();
        let link = document.createElement("a");
        if (link.download !== undefined) { // feature detection
            // Browsers that support HTML5 download attribute
            var url = URL.createObjectURL(blob);
            link.setAttribute("href", url);
            link.setAttribute("download", filename);
            link.style.visibility = 'hidden';
            document.body.appendChild(link);
            link.click();
            document.body.removeChild(link);
        }

        const csvFormatTimeEnd = new Date().getTime();
        const csvFormatTime = csvFormatTimeEnd - csvFormatTimeStart;
        console.log('regular download takes ' + csvFormatTime + ' ms to run');
    }

    clickEnd = new Date().getTime();
    console.log('The whole process took: ' + (clickEnd - clickStart) + ' ms');
}

기사는 신용이 있어야 합니다.

React Function 구성 요소에서 수행하는 방법은 다음과 같습니다.

const [productTemplateUrl, setProductTemplateUrl] = useState<string | undefined>(undefined,);
const downloadRef = useRef<HTMLAnchorElement>(null);

const getTemplate = async () => {
  const res = await getProductTemplate();
  const url = window.URL.createObjectURL(new Blob([res]));
  setProductTemplateUrl(url);
  if (downloadRef != null) {
    downloadRef?.current?.click();
  }
};

<a
  style={{ display: 'none' }}
  download="product-template.csv"
  href={productTemplateUrl}
  ref={downloadRef}
/>

이것은 제가 최근에 다운로드할 때 사용한 React 기능 구성 요소입니다.TypeScript로의 변환은 매우 간단합니다.

import React, { useEffect, useRef } from 'react'
import PropTypes from 'prop-types'
import styled from 'styled-components'

const Link = styled.a`
  display: none;
`
const Downloader = ({ blob, filename, onDownload }) => {
  const link = useRef(null)
  const url = URL.createObjectURL(blob)

  useEffect(() => {
    link.current.click()
    onDownload()

    return () => {
      URL.revokeObjectURL(url)
    }
  }, [url, onDownload])

  return (
    <Link ref={link} href={url} download={filename}>
      Table export
    </Link>
  )
}

Downloader.propTypes = {
  blob: PropTypes.object.isRequired,
  filename: PropTypes.string.isRequired,
  onDownload: PropTypes.func.isRequired
}

export { Downloader }

다음은 사용 방법에 대한 간단한 예입니다.

const { useRef, useEffect, useCallback, useState } = React
const { styled } = window
const root = ReactDOM.createRoot(document.getElementById('root'))
const Link = styled.a`
  display: none;
`
const Downloader = ({ blob, filename, onDownload }) => {
  const link = useRef(null)
  const url = URL.createObjectURL(blob)

  useEffect(() => {
    console.log('download url', url)
    link.current.click()
    onDownload()

    return () => {
      URL.revokeObjectURL(url)
    }
  }, [url, onDownload])

  return (
    <Link ref={link} href={url} download={filename}>
      Table export
    </Link>
  )
}
const App = () => {
  const [download, setDownload] = useState(false)
  const [file, setFile] = useState(null)
  const handleFileChange = useCallback((evt) => {
    setFile(evt.target.files[0])
  }, [setFile])
  const handleDownload = useCallback(() => {
    setDownload(true)
  }, [setDownload])
  const onDownload = useCallback(() => {
    console.log('download finished')
    setDownload(false)
  }, [setDownload])
 
  return (
    <div>
      <form>
        <input type="file" name="some-file" onChange={handleFileChange} />
      </form>
      {file && (
        <button onClick={handleDownload}>Download file</button>
      )}
      {file && download && (
        <Downloader blob={file} filename={file.name} onDownload={onDownload} />
      )}
    </div>
  )
}

root.render(<App />)
<script src="https://unpkg.com/react@18/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/react-is/umd/react-is.production.min.js"></script>
<script src="https://unpkg.com/styled-components/dist/styled-components.min.js"></script>
<div id="root"></div>

언급URL : https://stackoverflow.com/questions/31214677/download-a-reactjs-object-as-a-file

반응형