【Bug】多文件上传只有最后一个loading会关闭
问题复现
多文件上传,只有最后一个loading 会关闭,其他 loading 会一直为 true 。
解决方案
分案分析
简单来说,直接调用 onSuccess
和将其包装在 setTimeout(..., 0)
中的核心区别在于执行时机和任务类型(宏任务 vs 微任务/同步任务),这会影响到 Ant Design Vue 的 a-upload
组件如何处理这些连续的状态更新。
直接调用 onSuccess
(可能导致问题的情况):
-
同步或微任务的快速连续执行:
选择多个文件时,a-upload
组件可能会非常迅速地为每个文件依次调用customRequest
方法。如果onSuccess
是直接调用的(或者如果customRequest
是一个立即resolve
的async
函数,那么onSuccess
会被放入微任务队列),这意味着对于多个文件,这些onSuccess
调用会非常密集地发生,几乎在同一个事件循环的“tick”内或者连续的微任务中执行。 -
组件内部状态更新的竞争或覆盖:
Ant Design Vue 的a-upload
组件内部需要维护一个文件列表(fileList
),并且每个文件对象都有自己的状态(如uploading
,done
,error
)。当onSuccess
被调用时,组件会找到对应的文件对象并更新其状态。
如果这些更新指令来得太快,可能会发生以下情况:- 状态更新竞争:组件内部的更新逻辑可能还没完全处理完前一个文件的状态变更(包括触发 Vue 的响应式更新和 DOM 变更),下一个文件的更新指令就来了。这可能导致内部状态不一致或某些更新被“跳过”或“覆盖”。
- Vue 的批量更新机制:Vue 会将同一个“tick”内的多次数据变更合并,进行一次性的 DOM 更新。如果
a-upload
组件内部对fileList
中多个文件状态的修改过于集中,Vue 的批量更新机制可能无法如预期那样分别渲染每个文件的状态变化,尤其是在组件内部逻辑对这种高速并发更新处理不够完美的情况下。 - 组件的内部逻辑限制:某些组件可能设计上并没有针对这种极端密集的、几乎同时的多个子项状态更新做足优化,导致只有最后几个更新能被正确处理和渲染。
使用 setTimeout(..., 0)
(通常能解决问题的情况):
-
将任务推迟到宏任务队列:
setTimeout(callback, 0)
并不会立即执行callback
。它会将callback
放入宏任务队列 (Macrotask Queue)。这意味着callback
(包含onSuccess
的调用) 会在当前的同步代码执行完毕、所有微任务 (Microtasks, 如 Promise 的.then()
) 执行完毕之后,并且在浏览器下一次事件循环的宏任务阶段才会被取出执行。 -
为组件提供“喘息”机会:
通过这种方式,每个文件的onSuccess
调用都被分散到不同的宏任务中执行。- 分离更新逻辑:当第一个文件的
onSuccess
通过setTimeout
被调度执行时,它会在一个新的宏任务中完成。此时,a-upload
组件有充足的时间处理这一个文件的状态更新,Vue 也有机会完成相关的响应式变更和 DOM 更新。 - 避免竞争:到下一个文件的
onSuccess
(同样在它自己的宏任务中)被执行时,前一个文件的处理通常已经稳定下来。这大大减少了内部状态更新的竞争和覆盖风险。 - 逐个处理:组件可以更从容地、逐个地处理每个文件的状态变化,确保其内部数据和视图的一致性。
- 分离更新逻辑:当第一个文件的
打个比方:
- 直接调用:同时向一位客服抛出5个不同的请求,客服可能手忙脚乱,只记住了最后一个或漏掉了一些。
setTimeout(..., 0)
:将5个请求写在纸条上,每隔一小段时间(一次事件循环)递给客服一张。客服有时间处理完上一张,再看下一张,从而能准确处理所有请求。