Ответ 1
В настоящее время технология браузера не поддерживает загрузку файла непосредственно из запроса Ajax. Работа вокруг заключается в том, чтобы добавить скрытую форму и отправить ее за кулисы, чтобы заставить браузер активировать диалог "Сохранить".
Я запускаю стандартную реализацию Flux, поэтому я не уверен, какой именно код Redux (Reducer) должен быть, но рабочий процесс, который я только что создал для загрузки файла, выглядит следующим образом:
- У меня есть компонент React, называемый
FileDownload
. Вся эта составляющая делает визуализацию скрытой формы, а затем внутриcomponentDidMount
, немедленно отправьте форму и назовите ееonDownloadComplete
prop. - У меня есть еще один компонент React, мы будем называть его
Widget
, с кнопкой загрузки/значком (многие фактически... по одному для каждого элемента в таблице).Widget
имеет соответствующие действия и хранит файлы.Widget
импортируетFileDownload
. -
Widget
имеет два метода, связанных с загрузкой:handleDownload
иhandleDownloadComplete
. -
Widget
У магазина есть свойствоdownloadPath
. По умолчанию установлено значениеnull
. Когда значение установлено наnull
, процесс загрузки файла не выполняется, а компонентWidget
не отображает компонентFileDownload
. - Нажатие кнопки/значка в
Widget
вызывает методhandleDownload
, который запускает действиеdownloadFile
. ДействиеdownloadFile
НЕ делает запрос Ajax. Он отправляет событиеDOWNLOAD_FILE
в хранилище, отправляя вместе с нимdownloadPath
для загружаемого файла. Магазин сохраняетdownloadPath
и испускает событие изменения. - Поскольку теперь существует
downloadPath
,Widget
будет передаватьFileDownload
в необходимые реквизиты, включаяdownloadPath
, а также методhandleDownloadComplete
в качестве значения дляonDownloadComplete
. - Когда
FileDownload
отображается и форма отправляется сmethod="GET"
(POST тоже должен работать) иaction={downloadPath}
, ответ сервера теперь вызывает диалог браузера Save для целевого файла загрузки (проверенный в IE 9/10, последние версии Firefox и Chrome). - Сразу после отправки формы вызывается
onDownloadComplete
/handleDownloadComplete
. Это вызывает другое действие, которое отправляет событиеDOWNLOAD_FILE
. Однако на этот разdownloadPath
установлено значениеnull
. Магазин сохраняетdownloadPath
какnull
и испускает событие изменения. - Поскольку не существует
downloadPath
, компонентFileDownload
не отображается вWidget
, а мир - счастливое место.
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;
FileDownload.js
- полный, полностью функциональный код, готовый для копирования и вставки
- Реагировать 0.14.7 с Babel 6.x [ "es2015", "реагировать", "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>
);
}
}