与默认文件列表一起提供时,上传组件的行为不符合预期
Posted
技术标签:
【中文标题】与默认文件列表一起提供时,上传组件的行为不符合预期【英文标题】:Upload component not behaving as expected when supplied with the default file list 【发布时间】:2019-08-31 13:45:54 【问题描述】:我在antd upload 上编写了一个小组件,用户可以使用该组件将多个文件上传到服务器。我花了很多时间调试,但无法理解它的一些行为。该组件如下所示:
我面临两个问题:
每当组件获得包含已上传到服务器的文件的prefil
时,我都无法添加新文件。当我点击Add Another
后尝试上传一个新文件时,如下所示
组件重新回到最初有 2 个文件的默认状态。我只是想不通我该如何处理。
当我尝试通过单击关闭图标来删除默认文件之一时,在我单击Add another
后它再次出现。
我知道某处,我无法正确管理组件的状态,但我无法弄清楚自己。这是使用 typescript 编写的组件代码。
import Button, Icon, Form, Input, Upload, message from "antd";
export type DefaultFileList =
uid: number | string;
name: string;
status?: string;
url: string;
fileLabel: string;
;
type state =
uploadFieldIdContainer: number[];
mocAddErrorDescription?: string;
uploadMap: [index: number]: UploadFile[] ;
defaultMap:
[index: number]:
default: DefaultFileList[];
fileLabel: string;
;
;
;
type oprops =
prefil: DefaultFileList[];
buttonLabel?: string;
type: string;
;
export default class DocumentUploader extends Component<
FormComponentProps & oprops,
state
>
private maxUploadPerButton: number;
constructor(props)
super(props);
this.maxUploadPerButton = 1;
const dMap = this.prepareDefaultFileMap();
this.state =
uploadFieldIdContainer: this.getTotalDefaultDocuments(),
uploadMap: ,
defaultMap: dMap
;
this.addUploadFormField = this.addUploadFormField.bind(this);
this.removeUploadField = this.removeUploadField.bind(this);
getTotalDefaultDocuments()
if (this.props.prefil && Array.isArray(this.props.prefil))
return Array.from( length: this.props.prefil.length , (_, k) => k + 1);
else
return [];
prepareDefaultFileMap()
if (this.props.prefil && this.props.prefil.length == 0)
return ;
else
const dMap = ;
for (let i = 0; i < this.props.prefil.length; i++)
const p = this.props.prefil[i];
const flabel = p.fileLabel;
//delete p.fileLabel;
dMap[i + 1] =
default: [p],
fileLabel: flabel
;
return dMap;
async componentDidMount()
componentWillReceiveProps(nextProps: FormComponentProps & oprops)
if (this.props.prefil.length > 0)
this.setState(
uploadFieldIdContainer: this.getTotalDefaultDocuments(),
defaultMap: this.prepareDefaultFileMap()
);
removeUploadField(key: number, event: React.MouseEvent<htmlElement>)
event.preventDefault();
/**
* @see https://ant.design/components/form/?locale=en-US#components-form-demo-dynamic-form-item
*/
this.setState(prevState => (
uploadFieldIdContainer: prevState.uploadFieldIdContainer.filter(
field => field !== key
)
));
getUploadFileProps(key: number): [index: string]: any
const _this = this;
const defaultMap = this.state;
const fileList = this.state.uploadMap[key] || [];
const defaultFileList = (defaultMap[key] && defaultMap[key].default) || [];
const props =
name: "file",
action: getDocumentStoreUploadApi(),
headers: HttpClient.requestConfig(),
onPreview: (file: [index: string]: any ) =>
getFileFromDocumentStore(file.url, file.name);
,
beforeUpload(file: File, fileList: File[])
if (file.type.match(/image/gi))
return false;
else
return true;
,
multiple: false,
onChange(info: [index: string]: any )
console.log("changed..");
let fileList = info.fileList;
// 1. Limit the number of uploaded files
// Only to show 1 recent uploaded file, and old ones will be replaced by the new
fileList = fileList.slice(-1 * _this.maxUploadPerButton);
// 2. Read from response and show file link
fileList = fileList.map((file: [index: string]: any ) =>
if (file.response)
// Component will show file.url as link
file.url = file.response.url;
return file;
);
const uploadMap = _this.state;
Object.assign(uploadMap, [key]: fileList );
_this.setState(
uploadMap
);
if (info.file.status === "done")
message.success(`$info.file.name file uploaded successfully`);
else if (info.file.status === "error")
message.error(`$info.file.name file upload failed.`);
;
if (fileList.length > 0)
Object.assign(props, fileList );
else if (defaultFileList.length > 0)
Object.assign(props, defaultFileList );
return props;
getUploadField(key: number)
const getFieldDecorator = this.props.form;
const defaultMap = this.state;
const documentLabel = (defaultMap[key] && defaultMap[key].fileLabel) || "";
return (
<div className="d-flex justify-content-between">
<div className="inline-block w-55">
<FormItem label="Select File">
getFieldDecorator(`selected_file_$this.props.type[$key]`,
rules: [
required: "undefined" === typeof defaultMap[key],
message: "Please select the file to upload"
]
)(
// <input type="file" id="input">
<Upload ...this.getUploadFileProps(key)>
<Button disabled=false>
<Icon type="upload" /> Click to Upload
</Button>
</Upload>
)
</FormItem>
</div>
<div className="inline-block w-45">
<FormItem label="File Label">
getFieldDecorator(
`selected_file_label_$this.props.type[$key]`,
rules: [
required: true,
message: "Please input the file label"
],
initialValue: documentLabel
)(<Input type="text" />)
</FormItem>
</div>
<div className="inline-block pointer d-flex align-items-center">
<span>
<Icon
type="close"
onClick=this.removeUploadField.bind(this, key)
/>
</span>
</div>
</div>
);
addUploadFormField(event: React.MouseEvent<HTMLElement>)
event.preventDefault();
const uploadFieldIdContainer = this.state;
// We only keep inside the state an array of number
// each one of them represent a section of fields.
const lastFieldId =
uploadFieldIdContainer[uploadFieldIdContainer.length - 1] || 0;
const nextFieldId = lastFieldId + 1;
this.setState(
uploadFieldIdContainer: uploadFieldIdContainer.concat(nextFieldId)
);
getMainUploadButton()
return (
<div className="d-flex w-100 mt-3">
<Button
type="primary"
ghost=true
className="w-100 letter-spacing-1"
onClick=this.addUploadFormField
>
<Icon type="plus-circle" />
this.props.buttonLabel || "Select File(s) To Upload"
</Button>
</div>
);
getUploadFieldFooter()
return (
<div className="d-flex justify-content-between small">
<div className="inline-block">
<Button
type="primary"
shape="circle"
icon="plus"
ghost=true
size="small"
className="d-font mr-1"
onClick=this.addUploadFormField
/>
<div
className="text-primary pointer d-font inline-block letter-spacing-1 mt-1"
onClick=this.addUploadFormField
>
Add another
</div>
</div>
</div>
);
render()
const uploadFieldIdContainer = this.state;
const mocButton = this.getMainUploadButton();
const toRender =
uploadFieldIdContainer.length > 0 ? (
<div>
<div className="w-100 p-2 gray-background br-25">
uploadFieldIdContainer.map(fieldIndex => (
<div key=fieldIndex>this.getUploadField(fieldIndex)</div>
))
this.getUploadFieldFooter()
</div>
</div>
) : (
mocButton
);
return toRender;
render
是渲染所有输入字段的主要方法。上述组件使用如下:
<DocumentUploader
form=this.props.form
prefil=[
uid: "somehash",
name: "name",
url: "url",
fileLabel: "label"
]
type="test"
/>
我必须重申,只有在使用已上传到服务器的文件初始化组件时才会出现问题,并且在重新尝试使用组件时(即第一次上传时)工作得很好。
【问题讨论】:
【参考方案1】:如果我正确理解您的代码,我认为this.props.prefil
包含上传到服务器上的文件。如果正确,则需要更改componentWillReceiveProps
的代码,使其只运行一次,如下所示。
首先,您可以将初始状态设置为:
this.state = updateFlag: true;
然后在componentWillReceiveProps
中作为:
componentWillReceiveProps(nextProps: FormComponentProps & oprops)
if (this.props.prefil.length > 0 && this.state.updateFlag)
this.setState(
uploadFieldIdContainer: this.getTotalDefaultDocuments(),
defaultMap: this.prepareDefaultFileMap(),
updateFlag: false,
);
【讨论】:
以上是关于与默认文件列表一起提供时,上传组件的行为不符合预期的主要内容,如果未能解决你的问题,请参考以下文章