Vue.js无限滚动时v-for卡顿,新增数据触发全量重渲染如何解决?
问题根因
- 列表更新频次过高:遍历获取到的帖子数据时,每拿到一条就通过
this.postList = [...this.postList, doc]覆盖整个数组,单页拉取5条数据就会触发5次全列表重渲染。 - 重复触发响应式更新:将文档加入列表后才异步拉取评论数并修改
doc.commentCount,已进入响应式数组的对象属性变更会再次触发列表重渲染,进一步增加性能开销。 - 滚动事件无节流:scroll事件触发频率可达每秒几十次,未做节流处理会占用大量主线程资源,加剧卡顿。
修复方案
1. 优化数据更新逻辑
先批量拉取所有帖子数据、异步并行获取所有帖子的评论数,所有数据处理完成后再一次性更新到postList中,全程仅触发1次列表更新。
2. 新增滚动事件节流
限制滚动事件处理函数的触发频率,降低非必要的计算开销。
3. 可选组件渲染优化
如果已渲染的旧帖子卡片不需要动态更新数据,可在Postcard组件上添加v-once指令,强制组件仅渲染一次,后续更新时跳过旧组件的diff逻辑。
修改后的代码
JavaScript部分
// 先实现节流工具函数 function throttle(func, delay) { let lastRun = 0; return function() { const now = Date.now(); if (now - lastRun >= delay) { lastRun = now; func.apply(this, arguments); } } } var vueApp = new Vue({ el: '#root', data: { postList: [], isOkaytoLoad: false, isRoomPostEmpty: false, }, mounted: function() { // 给滚动事件加300ms节流 window.addEventListener('scroll', throttle(this.handleScroll, 300)); }, methods: { handleScroll: async function() { var d = document.documentElement; var offset = d.scrollTop + window.innerHeight; var height = d.offsetHeight - 200; if (this.isOkaytoLoad && offset >= height) { this.isOkaytoLoad = false; const lastPost = this.postList[this.postList.length - 1]; const room_id = PARAMETERS.get('room'); const q = query(collection(db, 'posts'), where('room', '==', room_id), orderBy("time", "desc"), limit(5), startAfter(lastPost)); const roomSnapshot = await getDocs(q); // 批量处理所有帖子和评论请求 const newPostPromises = roomSnapshot.docs.map(async (doc) => { const commentQ = query(collection(db, 'comments'), where('post_id', '==', doc.id)); const commentSnapshot = await getDocs(commentQ); // 先给doc加上评论数再加入数组 doc.commentCount = commentSnapshot.size; return doc; }); // 等待所有帖子数据处理完成 const newPosts = await Promise.all(newPostPromises); // 一次性把所有新帖子加入列表,仅触发一次更新 this.postList.push(...newPosts); setTimeout(()=>{ this.isOkaytoLoad = true }, 1000); } } } })
模板部分(可选优化)
<div v-if="postList.length > 0" class="card-containers"> <Postcard v-for="post in postList" :key="post.id" v-once <!-- 旧卡片不需要更新时添加,可大幅降低重渲染开销 --> :post-id="post.id" :owner-name="post.data().owner_displayName" :owner-uid="post.data().owner_id" :post-type="post.data().post_type" :image-url="post.data().image_url" :post-content="truncateString(linkify(post.data().post_content))" :room="post.data().room" :time="post.data().time.toDate()" :likers="post.data().likers" :comment-count="post.commentCount" :file-url="post.data().file_url" :file-name="post.data().file_name" :downloads="post.data().downloads"> </Postcard> </div>
内容的提问来源于stack exchange,提问作者Dev-Raldin
相关产品推荐
相关产品推荐

