遍历数据时生成多数组而非单数组的JS Firebase问题排查
问题:预约管理项目中数据重复渲染问题排查
我正在学习JavaScript,开发一个预约管理学习项目。从Firebase获取数据后存入数组对象按日期排序,但页面显示的条目远多于数据库实际的10条记录。执行console.log(allRecords)时得到10个数组而非单个数组,相关代码及数据库结构如下:
// Showing appointments of the current and previous month. let startDateArr = [] let startMonthArr = [] let allRecords = [] let update = false const showingApts = (doc) => { let newTime = new Date(doc.data().dateInMills.at(-1) * 1000) allRecords.push({ id: doc.id, name: doc.data().aptName, slot: doc.data().aptTimeSlot, email: doc.data().aptEmail, day: doc.data().aptDay.at(-1), type: doc.data().aptType, date: doc.data().aptStartDate.at(-1), month: doc.data().aptStartMonth.at(-1), year: doc.data().aptStartYear.at(-1), status: doc.data().appointmentStatus, softDelete: doc.data().softDelete, time: newTime }) allRecords.sort(function(a, b) { return a.time - b.time }) // Fetching names from profiles db. db.collection('profiles').onSnapshot((querySnapshotProfiles) => { querySnapshotProfiles.forEach((profileDoc) => { if (profileDoc.data().aptEmail.at(-1) === doc.data().aptEmail) { startDateArr.push(doc.data().aptStartDate.at(-1)) startMonthArr.push(doc.data().aptStartMonth.at(-1)) } }) }) setTimeout(() => { let aptStartDate = startDateArr.at(-1) let aptStartMonth = startMonthArr.at(-1) if (doc.data().aptStartDate.at(-1) === aptStartDate && doc.data().aptStartMonth.at(-1) === aptStartMonth) { return update = true } for (let record of allRecords) { let currentMonthAppointments = ` <div class="flex flex-col justify-center align-middle tableRow12"> <div class="grid grid-cols-6 text-center py-4 place-items-center text-xs border-b border-gray-200 hover:bg-blue-100 ease-in-out duration-300 text-blue-600 font-semibold px-2" data-id="${ record.id }"> <span> <span class='scheduleName'>${ record.name } </span> <span class='scheduleEmail block text-[10px] text-gray-400 font-medium'>${ record.email } </span> <div class='text-center w-full mt-2 font-medium'><button class='addMoreApt ${ update === true ? 'inline' : 'hidden' } text-center rounded-md bg-rose-600 p-2 text-white cursor-pointer text-[10px] '>Add 5 more appointments</button></div> </span> <span>${ record.slot }</span> <span> <span> ${ record.day }, </span> <span>${ record.date }-${ record.month }-${ record.year } </span> </span> <span>${ record.type }</span> </div> </div> ` scheduleTableRows.innerHTML += currentMonthAppointments } }, 1000) } // Clearing old rows in the table const rowsToDelete = () => { let tableRowsToDelete = document.querySelectorAll('.tableRow12') for (let rowsToDelete of tableRowsToDelete) { rowsToDelete.remove() } }
数据库结构
- 预约记录结构:

- Profile数据结构:

问题原因
- 全局数组未重置,重复累加:
allRecords是全局变量,每次调用showingApts都会往数组里追加新数据,没有在每次数据更新时清空,导致数组持续膨胀。 - 嵌套实时监听重复触发:在
showingApts内部嵌套profiles集合的onSnapshot实时监听,每条预约数据都会触发一次profile监听,多次执行startDateArr和startMonthArr的push操作,进一步加剧数据冗余。 - 渲染逻辑错误:循环追加所有历史数据:每次
showingApts执行后,setTimeout内会遍历整个allRecords数组并将内容追加到DOM,而非仅渲染当前新增记录。比如处理第1条数据时渲染10条,处理第2条时渲染11条,最终总条数为10+9+...+1=55条,远多于实际的10条。
解决方法
1. 重置全局数组,避免累加
在预约数据的主查询回调开头,清空所有全局数组,确保每次处理都是基于最新的数据库数据:
// 假设主查询入口是这样,在onSnapshot回调开头重置数组 db.collection('appointments').onSnapshot(querySnapshot => { allRecords = []; startDateArr = []; startMonthArr = []; querySnapshot.forEach(showingApts); // 统一调用渲染函数,不在showingApts内单独渲染 renderAppointments(); });
2. 分离数据获取与渲染,取消嵌套监听
提前缓存profile数据,避免在showingApts内嵌套实时监听,减少重复触发:
// 提前缓存所有profile数据 let profileCache = {}; db.collection('profiles').get().then(querySnapshot => { querySnapshot.forEach(doc => { const email = doc.data().aptEmail.at(-1); profileCache[email] = { startDate: doc.data().aptStartDate.at(-1), startMonth: doc.data().aptStartMonth.at(-1) }; }); }); // 修改showingApts,从缓存取数据 const showingApts = (doc) => { let newTime = new Date(doc.data().dateInMills.at(-1) * 1000); const aptEmail = doc.data().aptEmail; const profileData = profileCache[aptEmail]; allRecords.push({ id: doc.id, name: doc.data().aptName, slot: doc.data().aptTimeSlot, email: aptEmail, day: doc.data().aptDay.at(-1), type: doc.data().aptType, date: doc.data().aptStartDate.at(-1), month: doc.data().aptStartMonth.at(-1), year: doc.data().aptStartYear.at(-1), status: doc.data().appointmentStatus, softDelete: doc.data().softDelete, time: newTime }); if (profileData) { startDateArr.push(profileData.startDate); startMonthArr.push(profileData.startMonth); } allRecords.sort((a, b) => a.time - b.time); };
3. 统一渲染逻辑,避免重复追加
单独编写渲染函数,每次渲染前先清空旧行,再生成完整的DOM内容赋值给容器,而非追加:
const renderAppointments = () => { rowsToDelete(); // 先清空旧行 let html = ''; allRecords.forEach(record => { // 基于record信息判断update状态,替代全局变量 const update = startDateArr.includes(record.date) && startMonthArr.includes(record.month); html += ` <div class="flex flex-col justify-center align-middle tableRow12"> <div class="grid grid-cols-6 text-center py-4 place-items-center text-xs border-b border-gray-200 hover:bg-blue-100 ease-in-out duration-300 text-blue-600 font-semibold px-2" data-id="${ record.id }"> <span> <span class='scheduleName'>${ record.name } </span> <span class='scheduleEmail block text-[10px] text-gray-400 font-medium'>${ record.email } </span> <div class='text-center w-full mt-2 font-medium'> <button class='addMoreApt ${ update ? 'inline' : 'hidden' } text-center rounded-md bg-rose-600 p-2 text-white cursor-pointer text-[10px] '>Add 5 more appointments</button> </div> </span> <span>${ record.slot }</span> <span> <span> ${ record.day }, </span> <span>${ record.date }-${ record.month }-${ record.year } </span> </span> <span>${ record.type }</span> </div> </div> `; }); scheduleTableRows.innerHTML = html; // 直接赋值,而非追加 };
4. 移除不必要的setTimeout
通过缓存profile数据和统一渲染逻辑,无需再用setTimeout等待数据,确保数据就绪后直接渲染。
内容的提问来源于stack exchange,提问作者alittlecurryhot
相关产品推荐
相关产品推荐

