与默认文件列表一起提供时,上传组件的行为不符合预期

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&nbsp;
                </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,
        );
    

【讨论】:

以上是关于与默认文件列表一起提供时,上传组件的行为不符合预期的主要内容,如果未能解决你的问题,请参考以下文章

grep -l 在到 xargs 的管道上的行为不符合预期[重复]

变量的行为不符合预期

变量的行为不符合预期

变量的行为不符合预期

媒体查询在 Android 上的行为不符合预期

关闭堆栈中较低的 ViewController 的行为不符合预期