Vue 简易版无限加载组件实现原理 您所在的位置:网站首页 wordpress无限加载 Vue 简易版无限加载组件实现原理

Vue 简易版无限加载组件实现原理

2023-08-12 01:22| 来源: 网络整理| 查看: 265

背景

遇到的两个问题:scroll 事件不触发、如何将 loading 状态放在无限加载组件中进行管理。

无限加载组件在展示列表页数据时比较常见。特别是在 H5 列表页中,数据比较多,需要做分页,无限加载组件就是一个非常好的选择。

当列表页数据比较多时,一次性从服务端拿到所有的数据会比较耗时,长时间不展示列表数据,比较影响用户体验。所以对于一般的长列表数据,都会做分页。

首次请求时,只请求第一页数据;当用户上拉即将到达列表底部时,再请求下一页数据,将下一页数据拼接在之前的列表后。

mint-ui 无限加载组件体验地址:无限加载组件体验 实现功能

使用 vue3 composition API 实现如下功能:

InfinitView 组件:将 InfinitView 组件包裹在列表(项)外面即可实现无限加载。 节流加载:每次触底加载时,会自动节流,同一页数据只会请求一次(如果请求成功)。

注意:InfinitView 直接子元素高度需要比 InfinitView 组件高,才会触发滚动加载。InfinitView 组件的高度默认为其父元素的 100%。

Props // 触底距离,当距底部距离小于等于 distance 时,会触发加载函数 distance: { type: Number, default: 30, }, // 加载函数,触底时执行 onload: { type: Function, default: async () => {}, }, // 行内样式,在外部可以通过 classStyle 改变 InfinitView 组件的样式 classStyle: { type: Object, default: () => ({}), }, 复制代码 使用

直接将 InfiniteView 组件包裹在列表项外面即可:

{{ item }} 复制代码

使用 setTimeout 模拟列表数据的加载:

// nextPage 表示下一次请求哪一页的数据 const nextPage = ref(1); // list 表示数据列表 const list = ref(new Array(30).fill(0)); const onload = () => { return new Promise((resolve) => { setTimeout(() => { list.value.push(...new Array(50).fill(0).map((_, i) => i + 1 + (nextPage.value - 1) * 50)); nextPage.value++; resolve(nextPage.value); }, 200); }); } 复制代码

使用时需要注意:InfinitView 组件的高度默认为其父元素的 100%,如果其父元素高度不确定(例如:由子元素撑开),会导致 InfinitView 无法监听到滚动事件,也就不会触发 onload 函数(后面会解释原因)。

解决方案有两种:

为 InfinitView 组件的父元素设置一个可计算的高度。 为 InfinitView 组件设置一个可计算的高度,可通过其 props 行内样式 classStyle 设置,或者在外部给 InfinitView 组件加上类名及其样式。

注意:这里的可计算高度可以是:由 flex 弹性容器计算得来,但不能由子元素(InfinitView)撑开得来。

组件实现

组件的实现非常简单,InfinitView 组件实际上就是一个 div,只不过在 InfinitView 内部监听了该 div 的滚动事件。在即将触底的时候去调用从父组件中传过来的 onload 函数。

其 template 实现如下:

复制代码 可以通过 classStyle 在外部设置 InfinitView 组件的样式。 触发滚动事件的时候,会执行 onScroll 函数。onScroll 函数中屏蔽了调用 onload 函数的细节(触底加载、节流加载)。 使用 slot 将 InfinitView 的子组件当成该 div 的子组件。

其 style 样式如下:

.infinite-view { height: 100%; overflow-y: scroll; } 复制代码

InfinitView 组件的高度由其父元素决定,默认为其父元素高度的 100%,这就限制了其父元素的高度不能由 InfinitView 撑开决定。

其 script 如下:

import { ref, defineProps } from 'vue'; const props = defineProps({ distance: { type: Number, default: 30, }, onload: { type: Function, default: async () => {}, }, classStyle: { type: Object, default: () => ({}), }, }); const isloading = ref(false); const onScroll = async (element) => { if (isloading.value) { return; } if (element.scrollHeight { onScroll(e.target); }) }) 复制代码

上面的代码在 InfinitView 组件中,为 window 设置了监听 scroll 的事件。当屏幕滚动时,就会执行 onScroll 函数。这样也是没问题的,确实可以解决 scroll 事件不触发的问题。

但是我们并没有找到问题的根源,为什么在 InfinitView 组件中的 div 上面监听的 scroll 事件,却不会触发?

首先得知道什么情况下才会触发滚动事件:

父元素高度比其所有子元素高度之和小。 父元素的 overflow 属性值为:auto | scroll。

只有满足了以上两个条件,才会触发父元素的 scroll 事件。很多时候,某个 div 的 scroll 事件没有触发,是因为我们没有设置该 div 的高度,它的高度由子元素撑开,和子元素高度之和相等。

这样即使屏幕在滚动,触发的也不是它的 scroll 事件,而是更上层 div 的 scroll 事件。例如:如果某个 div 的高度由子元素撑开,并且其父元素高度确定,比它的高度小,则在滚动的时候,不会触发该 div 的 scroll 事件,会触发它的父元素的 scroll 事件。

或者我们忘记设置监听 scroll 事件的元素的 overflow 属性了,默认情况下,overflow 的值为 visible。

$emit 发射事件和 props 回调函数的区别

我们知道在 Vue 中,子组件向父组件通信有两种方式:通过 $emit 发射事件、通过调用父组件中传过来的回调函数。

这两种方式都可以由子组件向父组件通信,但是也有一些细微的区别:

通过调用父组件中传过来的回调函数可以拿到函数的返回值,而通过 $emit 发射事件不可以。 通过调用父组件中传过来的回调函数可以知道函数什么时候执行完,而通过 $emit 发射事件不可以,它只能将回调函数通过 $emit 的参数,传给父组件,强迫父组件显式调用,才能在函数执行完之后做一些事情。

看起来,好像使用 props 回调函数比 $emit 发射事件要更好,那是不是 $emit 发射事件就没有好处了呢?

也不是。从名字就可以看出,$emit 发射事件,是从子组件中发射一个事件给父组件,父组件在监听到子组件发射的事件之后,可以进行一系列的操作。它只是给父组件发射一个事件,传递一个信号给父组件,父组件接收到这个信号之后,接下来要怎么做还是由父组件决定。但是使用 props 回调函数的方式则不同,它是将父组件中的一个函数通过 props 传给子组件,子组件拿到这个回调函数之后,要怎么执行,完全取决于子组件。所以子组件可以知道回调函数什么时候执行完,也可以拿到回调函数的返回值。

了解了这两种通信方式的区别,就解决了如何将 loading 状态放在无限加载组件中进行管理。

因为需要将 loading 状态放在无限加载组件(子组件)中进行管理,所以无限加载组件(子组件)必须要知道请求什么时候回来,也就是 onload 异步函数什么时候执行完。

这样我们就可以用 props 回调函数的形式,将父组件中的异步请求函数传给子组件,当列表即将滚动到底部时,将 loading 状态置为 true,然后发送请求,当请求回来之后,异步函数执行完,再将 loading 状态置为 false。如果请求没有回来,loading 状态为 true,即使再次触发了 scroll 事件,也直接返回,不再继续发送请求。

const onScroll = async (element) => { // 如果上一次请求还没回来,直接 return if (isloading.value) { return; } if (element.scrollHeight


【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有