Vue中数组被Observer包裹顺序错乱,JSON.parse(JSON.stringify())失效如何解决
问题根源
- 顺序错乱和Vue响应式Observer、文件名特殊字符无关联,核心原因是
FileReader.readAsDataURL是异步操作。不同文件的加载完成时间受文件大小、浏览器处理效率影响,先加载完成的文件会先执行push操作写入planImages,最终数组顺序自然和原始files数组的顺序不一致。 - 之前错误操作的原因说明:
- JSON序列化得到空数组是因为执行序列化代码时,异步的
load事件还没有全部触发完成,planImages此时本身就没有填充完成,并非Observer导致的读取问题。 - 直接修改
__ob__属性属于违规操作,__ob__是Vue2响应式系统的内部标识,修改会直接破坏响应式逻辑,因此会抛出ob.observeArray is not a function错误。
- JSON序列化得到空数组是因为执行序列化代码时,异步的
解决方案
方案1:用Promise.all保证读取顺序(推荐)
利用Promise.all返回结果和传入Promise顺序严格一致的特性,保证写入数组的顺序和原始files顺序完全匹配,修改后的代码如下:
async updatePreviews() { if (this.plan.gallery == null || this.plan.gallery.length == 0) { this.plan.gallery = []; } if (this.files?.length == 0) { this.planImages = this.oldImages.filter((o) => this.planImages.map((o) => o.name).includes(o.name) ); return; } // 按files原始顺序包装文件读取Promise const fileReadPromises = this.files.map(file => { return new Promise(resolve => { // 过滤已存在的文件 if (this.planImages.some(g => g.name === file.name)) { return resolve(null); } const fileReader = new FileReader(); fileReader.readAsDataURL(file); fileReader.addEventListener("load", () => { resolve({ name: file.name, type: this.viewTypes[0], url: fileReader.result, description: "", }); }); }) }) // 等待所有文件读取完成,返回顺序和原始files完全一致 const readResults = await Promise.all(fileReadPromises); // 追加新文件到数组 this.planImages.push(...readResults.filter(Boolean)); // 如果需要强制按文件名拼音/字母排序,直接调用原生sort即可,Vue响应式数组支持原生方法 // this.planImages.sort((a, b) => a.name.localeCompare(b.name, 'zh-CN')); }
方案2:按索引占位
如果不想用async/await,也可以在遍历的时候先按索引占好数组位置,加载完成后替换对应位置的内容,同样能保证顺序:
updatePreviews() { if (this.plan.gallery == null || this.plan.gallery.length == 0) { this.plan.gallery = []; } if (this.files?.length == 0) { this.planImages = this.oldImages.filter((o) => this.planImages.map((o) => o.name).includes(o.name) ); return; } this.files.forEach((file, index) => { if (this.planImages.some(g => g.name === file.name)) return; // 先按索引占位,保证顺序 const placeholderIndex = this.planImages.length; this.planImages[placeholderIndex] = { name: file.name, type: this.viewTypes[0], url: '', description: "", }; const fileReader = new FileReader(); fileReader.readAsDataURL(file); fileReader.addEventListener("load", () => { // 加载完成后替换对应位置的url this.$set(this.planImages, placeholderIndex, { ...this.planImages[placeholderIndex], url: fileReader.result }) }); }); }
补充说明
Vue的响应式数组已经劫持了所有原生数组变异方法(push、sort、splice等),直接调用这些方法即可触发视图更新,不需要对__ob__属性做任何操作。如果你需要最终数组严格按字母顺序排列,在所有文件加载完成后直接调用sort方法即可。
内容的提问来源于stack exchange,提问作者Freder
相关产品推荐
相关产品推荐

