相信用 Upload 的很多老兄都遇到过这个问题,就是在处理上传文件时,从 onchange 方法中得到的 e.fileList 里面的 status 参数他么总是 uploading ,而不是我们需要的 done 状态,导致拿不到 thumbUrl 和 response 的 imgUrl 。
根据官网文档上对 这个问题 的解释,就是要注意两点:
- 其一是要保证 onchange 事件内部,至少有一次无阻碍的调用 setState 赋值 fileList,让所有状态变化都及时更新。当然这个“无阻碍”是我自己的理解,就是说 setState 的方法执行最好不写在 if 这类条件判断里面,直接写在最底部或者最顶部就行;
- 其二是要保证每次 setState 后 fileList 的状态都是最新的。所以,以上 issue 里面建议上一点我说的写法最好是:
this.setState({ fileList: [...this.state.fileList] })
不过对于以上第二点,个人亲测,即使直接 { fileList: e.fileList } 就可以。以下假定只传一张图片,一个完整的 onchange 事件内部处理可以像下面一样:
handleChange = e => {
if (e.file.status == 'done') {
if (e.file.response.code === 200) {
let result = e.fileList;
result[0].thumbUrl = result[0].response.imgUrl; //用绝对路径替代base64,有助于在保存图片的时候避免字段过长和减小数据库的压力
this.setState({ fileList: result });
} else {
Modal.warn({
title: '提示',
content: <span>{e.file.response.msg}</span>,
okText: 'ok',
});
}
}
this.setState({ fileList: e.fileList });
};
对应的react DOM结构可以是:
<FormItem label="上传图片">
{this.props.form.getFieldDecorator('file', { rules: [{ required: true, message: '请上传图片' }] })(
<div>
<Upload
name="file"
multiple={false}
headers={{ Accept: 'application/json', token: getToken() }}
action={this.state.imgUploadUrl}
listType="picture-card"
fileList={this.state.fileList}
onPreview={this.handlePreview}
onChange={this.handleChange}
accept="image/jpg, image/png"
>
{this.state.fileList.length >= 1 ? null : <div><Icon type="plus" /><div className="ant-upload-text">上传</div></div>}
</Upload>
<Modal visible={this.state.previewVisible} footer={null} onCancel={this.handleCancel}>
<img alt="example" style={{ width: '100%' }} src={this.state.previewImage} />
</Modal>
</div>
)}
</FormItem>
如上DOM结构,在一些常规操作中,上传图片的操作,很可能会存在这样的场景:新建记录、更新记录、获取记录详情、保存记录等等,同时配合使用 Form 以及 Form.Item 组件,以及 getFieldValue、setFieldValue、getFieldDecorator 等等 api 使 Upload 的操作受控。在使用 Upload 组件时,也许需要注意以下几点:
- 其一:在获取记录详情时,拿到图片的 url,一定要将 state 中的 fileList 的 status 状态设为 done:
this.setState({
previewImage: url,
fileList: [ {
thumbUrl: url,
status: 'done', // 这个必须,不然,图片自然显示不出来
uid: url, // 这个随意吧
} ],
});
// 以及将受控字段的值设为 url:
this.props.form.setFieldsValue( { file: bannerUrl } )
- 其二:在编辑图片的时候,比如想将图片替换掉,这儿在提交的时候,就要注意一下,看代码:
handleAddSubmit = () => {
const { form: { validateFields, setFieldsValue } } = this.props;
const { fileList } = this.state;
if (!fileList.length) {
// 编辑图片时候,如果删掉缩略图,fileList会被清空,不过 getFieldsValue 获取file 的值还在,这时候提交无errors,但是fileList是空的
setFieldsValue({ file: undefined });
}
validateFields((errors, values) => {
if (!errors) {
let data = {
url: fileList[0].thumbUrl,
}
// 以下做你的骚操作。。。
}
}
以上代码有一句注释,说啥意思呢?意思就是,我们在编辑图片的时候,可能想把图片换一张,就删掉了,然而,这个时候打了一秒钟瞌睡,醒来就搞忘了,就直接点提交。这个时候呢,本来 Form 组件在使用 getFieldDecorator 时,已经设置必填了,然而图片这个时候虽然看似删掉了,因为以上示例图片处已经变成一个上传提示按钮了,但是提交事件虽然没有提示以上 errors错误,但是 thumbUrl 却报错 undefined 了?这特么就尴尬了。
这啥原因呢?原因就是 validateFields 的函数参数 的values 参数中,file 字段的值(就是被删掉的图片的地址)并没有随着点击示例图上删除那样,把 file 字段的值重置,删除图片的操作只是把 fileList 状态重置清空了。这个时候提交结果是,Form.Item 处并没有 必填的错误提示,validateFields 通过了必填验证,然而,fileList 早已经变成了空数组。this.props.form.getFieldValue('file') 也就是上一张图的路径却还在。
所以,在操作这种情况时,要判断 fileList 是否为空,如果为空表示图片已经被删除,那么同时将 file 的状态同步一下,做了这个操作后,validateFields 验证空图片的时候,就会有错误提示“请上传图片”。
- 其三:这是个有点诡异的却又可能出现的,那就是,你可能已经以最规范的方式,操作了 Upload 组件,但是,onchange 事件中,e.file.status 死活都是 "uploading",硬不给你 "done",你说气人不?
这个时候,你就要看看,你的组件的 DOM 是否 持续 render 了。为啥这么说呢?因为,最上面已经说了,你要保证每次 setState 后 fileList 的状态都是最新的,虽然你 Upload 操作很规范,但是,fileList 的变化没法让 DOM 节点及时渲染,这个数据流的过程因为你的 DOM 或操作过 React 生命周期等等因素,没法及时反映到 DOM 渲染上。比如你 shouldComponentUpdate 那儿逻辑是不是有问题?封装组件的时候,哪儿忘了 fileList 是个引用类型,没有深层比较,或者因为其他原因,组件根本就不能触发 render。
收工!