在vue2和vue3中展示实现无限滚动加载数据列表的代码示例,分析数据分页和加载状态处理逻辑

大白话在vue2和vue3中展示实现无限滚动加载数据列表的代码示例,分析数据分页和加载状态处理逻辑

前端小伙伴们,有没有被“长列表加载”搞到头疼过?用户刷手机时,列表滚到底部半天没反应;或者手一抖滚太快,触发了N次重复请求;更尴尬的是——加载中的“转圈”图标突然消失,页面卡成PPT……今天咱们就用Vue2和Vue3,手把手教你实现丝滑的无限滚动加载,彻底解决这些糟心事!

一、长列表的"加载之痛"

先说说我做电商项目时踩的坑:商品列表用v-for直接渲染1000条数据,页面卡到用户投诉“划不动”;后来改成翻页按钮,用户又吐槽“每次都要手动点,麻烦”;最离谱的是——滚动到底部触发加载,结果网络慢的时候,用户重复滚动触发了3次请求,数据乱成一团。

这些痛点总结起来就3条:

  1. 性能卡顿:一次性渲染大量数据,页面响应慢;
  2. 重复请求:滚动过快导致多次触发加载;
  3. 状态混乱:加载中的提示、加载完成的文案没管好,用户体验差。

二、无限滚动的3个核心逻辑

无限滚动的本质是“滚动触发加载”,核心要解决3个问题:何时加载如何加载如何控制状态

1. 何时加载:滚动位置的判断

判断是否需要加载新数据,关键是计算“滚动条是否接近底部”。公式如下:

// 触发加载的条件:滚动高度 + 视口高度 ≥ 内容总高度 - 阈值(比如50px)
const shouldLoad = scrollTop + clientHeight >= scrollHeight - threshold;
  • scrollTop:滚动条距离顶部的距离;
  • clientHeight:视口高度(可见区域的高度);
  • scrollHeight:内容总高度(包括不可见部分);
  • threshold:阈值(提前50px触发加载,避免用户滚到底才开始加载)。

2. 如何加载:数据分页与防抖

为了避免重复请求,需要:

  • 分页控制:用page变量记录当前页码,每次加载后page++
  • 防抖处理:用lodash.debounce或自定义函数,确保滚动事件在短时间内只触发一次;
  • 请求锁:用isLoading状态标记“正在加载”,防止重复请求。

3. 如何控制状态:加载提示与边界处理

用户需要明确的反馈:

  • 加载中:显示“正在加载…”的提示;
  • 加载完成:显示“没有更多数据了”;
  • 加载失败:显示“加载失败,点击重试”。

三、代码示例:Vue2和Vue3的实现对比

(一)Vue2实现:选项式API版本

Vue2使用mountedbeforeDestroy生命周期管理滚动事件,用data存储状态,methods处理逻辑。

<template>
  <div class="infinite-list">
    <!-- 数据列表 -->
    <div class="list-item" v-for="item in list" :key="item.id">
      {{ item.content }}
    </div>

    <!-- 加载状态提示 -->
    <div class="load-status">
      <!-- 加载中 -->
      <div v-if="isLoading">⏳ 正在加载第{{ page }}...</div>
      <!-- 加载完成 -->
      <div v-else-if="hasMore">👇 继续滚动加载更多</div>
      <!-- 无更多数据 -->
      <div v-else>✨ 已加载全部数据</div>
      <!-- 加载失败 -->
      <div v-if="loadError" class="error-tip" @click="reload">
        ⚠️ 加载失败,点击重试
      </div>
    </div>
  </div>
</template>

<script>
import { debounce } from 'lodash'; // 引入防抖函数

export default {
  data() {
    return {
      list: [], // 数据列表
      page: 1, // 当前页码
      isLoading: false, // 加载状态
      hasMore: true, // 是否有更多数据
      loadError: false, // 加载失败状态
      threshold: 50, // 触发加载的阈值(px)
    };
  },

  mounted() {
    // 挂载时添加滚动监听(防抖处理,300ms内只触发一次)
    this.scrollHandler = debounce(this.checkScroll, 300);
    window.addEventListener('scroll', this.scrollHandler);
    // 初始加载第一页
    this.loadData();
  },

  beforeDestroy() {
    // 销毁时移除滚动监听,避免内存泄漏
    window.removeEventListener('scroll', this.scrollHandler);
  },

  methods: {
    // 检查滚动位置
    checkScroll() {
      // 如果正在加载或无更多数据,直接返回
      if (this.isLoading || !this.hasMore) return;

      // 获取滚动相关数值
      const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
      const clientHeight = document.documentElement.clientHeight;
      const scrollHeight = document.documentElement.scrollHeight;

      // 判断是否触发加载
      if (scrollTop + clientHeight >= scrollHeight - this.threshold) {
        this.loadData();
      }
    },

    // 加载数据
    async loadData() {
      this.isLoading = true; // 标记加载中
      this.loadError = false; // 重置错误状态

      try {
        // 模拟API请求(实际替换为你的接口)
        const res = await fetch(`https://api.example.com/data?page=${this.page}`);
        const data = await res.json();

        // 没有更多数据:如果返回数据为空,或接口返回hasMore为false
        if (data.length === 0 || !data.hasMore) {
          this.hasMore = false;
        } else {
          this.list = this.list.concat(data); // 合并新数据
          this.page++; // 页码+1
        }
      } catch (error) {
        this.loadError = true; // 标记加载失败
        console.error('加载失败:', error);
      } finally {
        this.isLoading = false; // 无论成功失败,都要结束加载状态
      }
    },

    // 重试加载(加载失败时调用)
    reload() {
      this.loadData();
    },
  },
};
</script>

<style scoped>
.infinite-list {
  min-height: 100vh; /* 确保列表有足够高度触发滚动 */
}
.list-item {
  padding: 16px;
  border-bottom: 1px solid #e5e7eb;
}
.load-status {
  padding: 16px;
  text-align: center;
  color: #6b7280;
}
.error-tip {
  color: #ef4444;
  cursor: pointer;
}
</style>

(二)Vue3实现:组合式API版本

Vue3使用setup函数和ref/reactive管理状态,用onMounted/onUnmounted生命周期,代码更简洁、逻辑更集中。

<template>
  <!-- 模板结构与Vue2完全一致 -->
  <div class="infinite-list">
    <div class="list-item" v-for="item in list" :key="item.id">
      {{ item.content }}
    </div>
    <div class="load-status">
      <div v-if="isLoading">⏳ 正在加载第{{ page }}...</div>
      <div v-else-if="hasMore">👇 继续滚动加载更多</div>
      <div v-else>✨ 已加载全部数据</div>
      <div v-if="loadError" class="error-tip" @click="reload">
        ⚠️ 加载失败,点击重试
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import { debounce } from 'lodash';

// 响应式状态
const list = ref([]);
const page = ref(1);
const isLoading = ref(false);
const hasMore = ref(true);
const loadError = ref(false);
const threshold = ref(50);

// 滚动监听函数(防抖处理)
const checkScroll = debounce(() => {
  if (isLoading.value || !hasMore.value) return;

  const scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
  const clientHeight = document.documentElement.clientHeight;
  const scrollHeight = document.documentElement.scrollHeight;

  if (scrollTop + clientHeight >= scrollHeight - threshold.value) {
    loadData();
  }
}, 300);

// 加载数据函数
const loadData = async () => {
  isLoading.value = true;
  loadError.value = false;

  try {
    const res = await fetch(`https://api.example.com/data?page=${page.value}`);
    const data = await res.json();

    if (data.length === 0 || !data.hasMore) {
      hasMore.value = false;
    } else {
      list.value = [...list.value, ...data]; // 合并新数据(Vue3推荐用展开语法)
      page.value++;
    }
  } catch (error) {
    loadError.value = true;
    console.error('加载失败:', error);
  } finally {
    isLoading.value = false;
  }
};

// 重试加载
const reload = () => {
  loadData();
};

// 生命周期:挂载时初始化
onMounted(() => {
  window.addEventListener('scroll', checkScroll);
  loadData(); // 初始加载
});

// 生命周期:卸载时清理
onUnmounted(() => {
  window.removeEventListener('scroll', checkScroll);
});
</script>

<style scoped>
/* 样式与Vue2一致,这里省略 */
</style>

四、Vue2 vs Vue3实现差异

对比项Vue2(选项式API)Vue3(组合式API)
状态管理分散在data对象中集中在ref/reactive中,逻辑更紧凑
生命周期mounted/beforeDestroyonMounted/onUnmounted
代码结构按类型(data/methods)组织按功能(滚动逻辑/加载逻辑)组织
响应式更新依赖Object.defineProperty基于Proxy,支持更多数据类型
维护性逻辑分散,长组件难以维护逻辑模块化,易于拆分复用
性能良好,但复杂场景可能有性能损耗更高效的响应式系统,性能更优

五、面试题回答方法

正常回答(结构化):

“无限滚动加载的核心是监听滚动事件,判断是否到达加载位置,结合分页逻辑请求数据,并控制加载状态。具体步骤:

  1. 滚动监听:通过addEventListener('scroll', handler)监听滚动事件,用防抖(debounce)优化性能;
  2. 位置判断:计算scrollTop + clientHeight ≥ scrollHeight - threshold,触发加载;
  3. 分页控制:用page变量记录当前页码,每次加载后递增;
  4. 状态管理:用isLoading防止重复请求,hasMore判断是否有更多数据,loadError处理加载失败;
    Vue2和Vue3的差异主要体现在API风格:Vue2用选项式API(data/methods),Vue3用组合式API(setup/ref),后者逻辑更集中,维护更方便。”

大白话回答(接地气):

“就像自动续杯的奶茶——用户滚到底部,系统自动‘续杯’下一页数据。关键是要判断‘杯子快空了’(滚动接近底部),然后‘续杯’(请求数据),同时告诉用户‘正在倒奶茶’(加载中)、‘奶茶倒完了’(无更多数据)。
Vue2和Vue3的区别就像用‘老式收音机’还是‘智能手机’:Vue2功能都有,但按钮分散;Vue3把功能集成到‘屏幕’上(组合式API),用起来更顺手。”

六、总结:3个核心步骤+2个避坑指南

3个核心步骤:

  1. 滚动监听:用防抖优化,避免频繁触发;
  2. 分页请求:控制page变量,合并新数据;
  3. 状态反馈:明确加载中、加载完成、加载失败的提示。

2个避坑指南:

  • 避免内存泄漏:一定要在组件卸载时移除滚动监听(removeEventListener);
  • 处理动态高度:如果列表项高度不固定,用IntersectionObserver替代滚动监听(下文扩展思考会讲)。

七、扩展思考:4个高频问题解答

问题1:列表项高度动态变化,滚动监听不准怎么办?

解答:用IntersectionObserver(交叉观察者)监听“加载触发区”是否可见。在列表底部加一个div.load-trigger,当它进入视口时触发加载:

<template>
  <div class="infinite-list">
    <!-- 列表项 -->
    <div class="list-item" v-for="item in list" :key="item.id">...</div>
    <!-- 触发加载的占位元素 -->
    <div ref="trigger" class="load-trigger"></div>
    <!-- 状态提示 -->
    <div class="load-status">...</div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';

const trigger = ref(null);
const observer = ref(null);

onMounted(() => {
  // 创建交叉观察者,阈值设为0(触发区进入视口即触发)
  observer.value = new IntersectionObserver((entries) => {
    const entry = entries[0];
    if (entry.isIntersecting && !isLoading.value && hasMore.value) {
      loadData();
    }
  }, { threshold: 0 });

  // 观察触发区
  observer.value.observe(trigger.value);
});

onUnmounted(() => {
  observer.value.disconnect(); // 卸载时断开观察者
});
</script>

问题2:如何优化滚动性能?

解答

  • 虚拟滚动:只渲染可见区域的列表项(如vue-virtual-scroller库),减少DOM节点数量;
  • 节流/防抖:滚动事件用debouncethrottle限制触发频率;
  • 避免强制同步布局:不要在滚动事件中读取scrollTop等会触发重排的属性(可缓存上一次的值)。

问题3:如何实现反向滚动加载(向上滚动加载历史数据)?

解答:监听scrollTop === 0(滚动到顶部),并判断是否需要加载上一页数据:

// 在checkScroll函数中添加
if (scrollTop === 0 && !isLoading.value && hasPrev) {
  loadPrevData(); // 加载上一页
}

问题4:如何与UI库(如Element UI)结合使用?

解答:UI库的el-scrollbar组件自带滚动事件,用@scroll监听即可,无需操作原生window对象:

<el-scrollbar @scroll="handleScroll">
  <div class="infinite-list">...</div>
</el-scrollbar>

<script>
// Vue2示例
methods: {
  handleScroll({ target }) {
    // target是el-scrollbar的滚动容器
    const scrollTop = target.scrollTop;
    const clientHeight = target.clientHeight;
    const scrollHeight = target.scrollHeight;
    // 判断加载条件...
  }
}
</script>

结尾:无限滚动的终极目标——“无感加载”

好的无限滚动应该让用户“察觉不到加载”:数据无缝衔接,提示清晰明确,滚动流畅不卡顿。通过Vue2和Vue3的实现对比,我们能看到Vue3在逻辑组织上的优势,但核心原理是相通的。下次再遇到长列表需求,你可以拍着胸脯说:“这题我会,5分钟搞定!”

如果这篇文章帮你理清了思路,别忘了点个赞~ 有任何问题,评论区见!咱们下期聊“如何用Vue3组合式API重构复杂表单”,不见不散!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

前端布洛芬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值