react redux-form 中异步提交文件

在之前看redux-form的时候, 只实现了两种比较简单的Field组件, TextFieldDateTimeField, 现在来看看一个麻烦一点的FileField.

文件上传是比较常用的功能, 尽量做到简单易用的组件化形式为好, 首先定义FileField组件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class FileField extends React.PureComponent {
static propTypes = {};
render() {
let {
input,
label,
inputFile,
labelClassName,
inputDivClassName,
className,
inputClassName,
meta: { touched, error, warning },
} = this.props;
return (
<div
className={classNames(className, 'has-feedback', {
'has-error': touched && error,
'has-warning': touched && !error && warning,
'has-success': touched && !error && !warning,
})}
>
<label className={classNames('control-label', labelClassName)}>{label}</label>
<div
className={classNames(inputDivClassName, {
'hint--bottom-right hint-error': touched && error,
'hint--bottom-right hint-warning': touched && !error && warning,
})}
aria-label={error || warning}
>
<div className='input-group cursor-p'>
<input type='text' readOnly className={classNames('form-control cursor-p', inputClassName)} {...input} />
<span className='input-group-addon'>
<span className='glyphicon glyphicon-file'></span>
</span>
</div>
</div>
<input ref='selectFileInput' type='file' className='hide' {...inputFile} />
</div>
);
}
}

然后给组件加入点击事件和触发打开弹窗的事件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<div className="input-group cursor-p" onClick={this.clickOpenSelectFileWindow}>
<input ref="selectFileInput" type="file" className="hide" onChange={this.clickSelectFileChange} {...inputFile} />

const mapDispatchToProps = (dispatch) => ({
fn: {
change: (form, field, value) => {
dispatch(change(form, field, value));
},
},
});
@connect(null, mapDispatchToProps)
class FileField extends React.PureComponent {
constructor(props) {
super(props);
this.state = {};
this.clickOpenSelectFileWindow = this.clickOpenSelectFileWindow.bind(this);
this.clickSelectFileChange = this.clickSelectFileChange.bind(this);
}
clickOpenSelectFileWindow(event) {
this.refs.selectFileInput.click();
if (event) {
event.preventDefault();
event.stopPropagation();
}
}
clickSelectFileChange(event) {
let fileName = event.target.files.length > 0 ? event.target.files[0].name : '';
this.props.fn.change(this.props.formName, this.props.input.name, fileName);
this.props.fn.change(this.props.formName, this.props.input.name + 'Files', event.target.files);
}
}
//使用
<Field name="SelectFile" component={FileField} label="选择文件" labelClassName="col-sm-2" inputDivClassName="col-sm-10" formName={toDoItemFormName} />

这样基本实现了点击输入框, 会弹出选择文件, 选择完成以后会显示选择文件的名字, 而选择到的文件保存在redux-formstate中同名field加后缀Files下. 在提交时只需要提取相应文件传入到后台即可. 这里使用jquery-form来实现异步提交文件.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
var postFile = function (data, files) {
var $form = $('<form><input name="CommandName" value="' + data.CommandName + '"/></form>');
var $newInput = $('<input name="myFile" type="file"/>');
$newInput[0].files = files;
$form.append($newInput);
var params = _.isString(data.params) ? data.params : JSON.stringify(data.params);
$form.append($('<input type="text" name="data" value="' + encodeURIComponent(params) + '"/>'));

var promise = new Promise((resolve, reject) => {
$form.ajaxSubmit({
url: url,
type: "POST",
dataType: "json",
success: function (resultValue) {
let action = resolveResultValue(resultValue);
if (action.action === ActionType.SUCCESS) {
return resolve(resultValue);
} else {
return reject(action);
}
},
error: function (resultValue) {
return reject({ action: ActionType.NETWORK_ERROR, payload: resultValue.responseText });
},
});
}).then((resultValue) => {
...//log
return resultValue.returnValue;
}).catch(({ action, payload }) => {
...//异常处理
return Promise.reject(payload);
})
return promise;
}
//service.js
saveOrUpdateItemWithFile: (info) => {
return commonAjaxService.postFile({
CommandName: 'Service$SaveOrUpdateInfoWithFiles',
params: info,
}, info.SelectFileFiles);
},
//action.js
static saveOrUpdateItemWithFile(info) {
return (dipatch, getState) => {
let action = {
type: ActionType.SAVE_ITEM_START,
payload: null,
};
dipatch(action);
dipatch(startSubmit(toDoItemFormName));
return Service.saveOrUpdateItemWithFile(info).then(
(resultValue) => {
dipatch(setSubmitSucceeded(toDoItemFormName));
let action = {
type: ActionType.SAVE_ITEM_END,
payload: resultValue,
};
dipatch(action);
},
(error) => {
dipatch(setSubmitFailed(toDoItemFormName));
let action = {
type: ActionType.SAVE_ITEM_END,
payload: null,
};
dipatch(action);
}
);
};
}

还有个情况是, 想在别的地方触发该文件选择框. 那么提供一个传入函数, 在函数返回当前组件实例(类似 ref), 在需要触发的时候执行ref.clickOpenSelectFileWindow(event)即可.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//file-field.js
static propTypes = {
componentRef: P.func.isRequired,
}
componentDidMount() {
if (this.props.componentRef) {
this.props.componentRef(this);
}
}
//使用
bindInputFileRef(ref){
this.inputFileRef = ref;
}
targetOpenFileSelectWindow(event){
this.inputFileRef.clickOpenSelectFileWindow(event);
}
<Field componentRef={bindInputFileRef} name="SelectFile" component={FileField} label="选择文件" labelClassName="col-sm-2" inputDivClassName="col-sm-10" formName={personToDoItemFormName} />
作者

Mosby

发布于

2017-10-29

许可协议

评论