Vue中v-for无法加载更多Firestore数据,如何实现无限滚动?
问题分析
你的代码存在三个核心问题导致无限滚动失效:
- 每次查询都从集合开头取4条数据,未记录上一次加载的最后位置,导致重复获取相同内容
- 每次加载直接用
Premium.value = ...覆盖原有数据,而非追加,v-for只能显示最新的4条 - 递归调用
load()的逻辑错误,会引发无限循环请求
解决方案
1. 改造组合式函数,实现分页游标与数据追加
添加游标变量记录最后一个文档位置,修改查询逻辑实现分页,同时改为追加数据而非覆盖:
import { projectFirestore } from "../Firebase/Config"; import { ref } from "vue" const getPremium = () => { const Premium = ref([]) const error = ref(null) const lastDoc = ref(null) // 记录最后一个文档,作为分页游标 const isLoading = ref(false) // 防止重复加载 const load = async () => { if (isLoading.value) return // 正在加载时不重复请求 isLoading.value = true try { let query = projectFirestore.collection('Premium').limit(4) // 如果有游标,从游标之后开始查询 if (lastDoc.value) { query = query.startAfter(lastDoc.value) } const res = await query.get() // 无更多数据时返回0 if (res.empty) { isLoading.value = false return 0 } // 更新游标为最后一个文档 lastDoc.value = res.docs[res.docs.length - 1] // 追加新数据到数组 const newData = res.docs.map(doc => { return {...doc.data(), id: doc.id} }) Premium.value = [...Premium.value, ...newData] isLoading.value = false return newData.length } catch (err) { error.value = err.message console.log(error.value) isLoading.value = false return 0 } } return { Premium, error, load, isLoading } } export default getPremium
2. 实现滚动触发加载逻辑
使用Intersection Observer监听页面底部触发元素,自动加载下一页:
<template> <div class="premium-list"> <div v-for="premium in Premium" :key="premium.id" class="premium-item"> <router-link :to="{ name: 'Samplepremium', params: { id: premium.id }}"> <div class="hover:scale-105 transition ease-in-out duration-300 bg-neutral-800 hover:bg-neutral-900 active:bg-neutral-900 text-neutral-400 font-bold rounded-xl"> <br> <p class="text-xl">{{ premium.Name }}</p> <div class="relative"> <img :src="premium.Pic1" class="object-contain px-1 inline-block w-96 h-44 md:w-60 md:h-80 lg:w-60 lg:h-80 xl:w-60 xl:h-80 2xl:w-60 2xl:h-80 transition ease-in-out duration-300"> <p class="bg-red-700 text-slate-300 text-xl absolute top-0 w-full">{{ premium.detail1 }}</p> <p class="bg-red-700 text-slate-300 text-xl absolute bottom-0 w-full">{{ premium.detail2 }}</p> </div> <div class="grid grid-cols-2 grid-rows-fit text-left ml-6"> <p>Age:</p> <p>{{ premium.detail3 }}</p> <p>Location:</p> <p>{{ premium.detail4 }}</p> <p>Rates:</p> <p>{{ premium.detail5 }} /hr</p> <p>Phone:</p> <p>{{ premium.detail6 }}</p> </div><br> </div> </router-link> </div> <!-- 触发加载的监听元素 --> <div ref="loadTrigger" class="loading-indicator"> {{ isLoading ? '加载中...' : (Premium.length > 0 ? '加载更多' : '暂无数据') }} </div> </div> </template> <script setup> import getPremium from '@/composables/getPremium' import { onMounted, ref } from 'vue' const { Premium, error, load, isLoading } = getPremium() const loadTrigger = ref(null) onMounted(() => { // 初始化加载第一页 load() // 创建Intersection Observer实例 const observer = new IntersectionObserver(async (entries) => { const entry = entries[0] if (entry.isIntersecting && !isLoading.value) { const count = await load() // 无更多数据时停止监听 if (count === 0) { observer.unobserve(loadTrigger.value) loadTrigger.value.textContent = '没有更多内容了' } } }, { threshold: 0.1 }) if (loadTrigger.value) { observer.observe(loadTrigger.value) } }) </script> <style scoped> .premium-list { min-height: 100vh; padding-bottom: 40px; } .loading-indicator { text-align: center; padding: 20px; color: #999; } </style>
关键说明
- 游标分页:通过
lastDoc记录最后一个文档,每次查询用startAfter()从下一个文档开始获取数据,避免重复加载 - 数据追加:使用扩展运算符把新数据加到数组末尾,保证v-for能展示所有已加载内容
- 加载状态控制:
isLoading变量防止滚动时重复触发请求 - Intersection Observer:比scroll监听性能更优,仅在触发元素进入视口时加载下一页
内容的提问来源于stack exchange,提问作者Epimetheus14
相关产品推荐
相关产品推荐

