You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 16:35:08