element-ui element-plus infinite-scroll - 分析

本文介绍 Element Plus 中 Infinite Scroll 指令的工作原理和技术实现细节,包括如何通过监听滚动事件触发加载更多数据的功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

源代码地址 - infinite-scroll

version:element-plus 1.0.1-beta.0

infinite-scroll是指令,不是组件
源码中涉及很多原生js属性,可以参考这里,对照观看

import { nextTick } from 'vue'
import { isFunction } from '@vue/shared'
import throttle from 'lodash/throttle'
import { entries } from '@element-plus/utils/util'
import { getScrollContainer, getOffsetTopDistance } from '@element-plus/utils/dom'
import throwError from '@element-plus/utils/error'
import type { ObjectDirective, ComponentPublicInstance } from 'vue'

export const SCOPE = 'ElInfiniteScroll'
export const CHECK_INTERVAL = 50
export const DEFAULT_DELAY = 200
export const DEFAULT_DISTANCE = 0

//可以传入的属性
const attributes = {
  // 节流时延,单位为ms
  delay: {
    type: Number,
    default: DEFAULT_DELAY,
  },
  // 触发加载的距离阈值,单位为px
  distance: {
    type: Number,
    default: DEFAULT_DISTANCE,
  },
  disabled: {
    type: Boolean,
    default: false,
  },
  // 是否立即执行加载方法,以防初始状态下内容无法撑满容器。
  immediate: {
    type: Boolean,
    default: true,
  },
}

type Attrs = typeof attributes
type ScrollOptions = { [K in keyof Attrs]: Attrs[K]['default'] }
type InfiniteScrollCallback = () => void
type InfiniteScrollEl = HTMLElement & {
  [SCOPE]: {
    container: HTMLElement | Window
    containerEl: HTMLElement
    instance: ComponentPublicInstance
    delay: number // export for test
    lastScrollTop: number
    cb: InfiniteScrollCallback
    onScroll: () => void
    observer?: MutationObserver
  }
}

const getScrollOptions = (el: HTMLElement, instance: ComponentPublicInstance): ScrollOptions => {
  return entries(attributes)
    .reduce((acm, [name, option]) => {
      // option就是attributes中的每个对象
      // 这里type 也是里面的 构造函数
      const { type, default: defaultValue } = option
      const attrVal = el.getAttribute(`infinite-scroll-${name}`)
      let value = instance[attrVal] ?? attrVal ?? defaultValue
      value = value === 'false' ? false : value
      // 这里直接用构造函数转换类型
      value = type(value)
      acm[name] = Number.isNaN(value) ? defaultValue : value
      return acm
    }, {} as ScrollOptions)
}

const destroyObserver = (el: InfiniteScrollEl) => {
  const { observer } = el[SCOPE]

  if (observer) {
    observer.disconnect()
    delete el[SCOPE].observer
  }
}
// scroll 方法
const handleScroll = (el: InfiniteScrollEl, cb: InfiniteScrollCallback) => {
  const {
    container, containerEl,
    instance, observer,
    lastScrollTop,
  } = el[SCOPE]
  const { disabled, distance } = getScrollOptions(el, instance)
  const { clientHeight, scrollHeight, scrollTop } = containerEl
  // 比较差值
  const delta = scrollTop - lastScrollTop

  el[SCOPE].lastScrollTop = scrollTop

  // trigger only if full check has done and not disabled and scroll down
  if (observer || disabled || delta < 0 ) return

  let shouldTrigger = false

  if (container === el) {
    // 判断是否小于设置的距离阈值
    shouldTrigger = scrollHeight - (clientHeight + scrollTop) <= distance
  } else {
    // get the scrollHeight since el might be visible overflow
    const { clientTop, scrollHeight: height } = el
    const offsetTop = getOffsetTopDistance(el, containerEl)
    shouldTrigger = scrollTop + clientHeight >= offsetTop + clientTop + height - distance
  }

  if (shouldTrigger) {
    cb.call(instance)
  }
}

function checkFull(el: InfiniteScrollEl, cb: InfiniteScrollCallback) {
  const { containerEl, instance } = el[SCOPE]
  const { disabled } = getScrollOptions(el, instance)

  if (disabled) return
  // 可滚动高度 <= 容器高度  那么直接调用用户自定的cb
  if (containerEl.scrollHeight <= containerEl.clientHeight) {
    cb.call(instance)
  } else {
    destroyObserver(el)
  }
}

const InfiniteScroll: ObjectDirective<InfiniteScrollEl, InfiniteScrollCallback> = {
  // 指令 https://vue3js.cn/docs/zh/guide/custom-directive.html#%E9%92%A9%E5%AD%90%E5%87%BD%E6%95%B0
  async mounted(el, binding) {
    // 指令 api 文档  https://vue3js.cn/docs/zh/api/application-api.html#directive
    // instance:使用指令的组件实例。
    // value:传递给指令的值。例如,在 v-my-directive="1 + 1" 中,该值为 2。
    const { instance, value: cb } = binding
    // 要求 v-infinite-scroll 绑定的值是函数
    if (!isFunction(cb)) {
      throwError(SCOPE, '\'v-infinite-scroll\' binding value must be a function')
    }

    // ensure parentNode mounted
    await nextTick()

    // 取到 属行中定义的 infinite-scroll-delay 和 infinite-scroll-immediate
    const { delay, immediate } = getScrollOptions(el, instance)
    const container = getScrollContainer(el, true)
    const containerEl = container === window ? document.documentElement : (container as HTMLElement)
    const onScroll = throttle(handleScroll.bind(null, el, cb), delay)

    if (!container) return

    el[SCOPE] = {
      instance, container, containerEl,
      delay, cb, onScroll,
      lastScrollTop: containerEl.scrollTop,
    }

    if (immediate) {
      // https://developer.mozilla.org/zh-CN/docs/Web/API/MutationObserver
      const observer = new MutationObserver(throttle(checkFull.bind(null, el, cb), CHECK_INTERVAL))
      el[SCOPE].observer = observer
      observer.observe(el, { childList: true, subtree: true })
      checkFull(el, cb)
    }

    container.addEventListener('scroll', onScroll)
  },
  unmounted(el) {
    const { container, onScroll } = el[SCOPE]

    container?.removeEventListener('scroll', onScroll)
    destroyObserver(el)
  },
}

export default InfiniteScroll

### v-infinite-scroll 下拉加载实现方法 `v-infinite-scroll` 是 Vue.js 中用于实现下拉加载功能的一个指令。通过该指令可以轻松监听滚动事件并动态加载更多数据,从而优化用户体验。 #### 实现方式 要使用 `v-infinite-scroll` 指令,首先需要安装对应的插件: ```bash npm install vue-infinite-scroll --save ``` 接着在项目的入口文件中引入并注册插件: ```javascript import Vue from 'vue'; import infiniteScroll from 'vue-infinite-scroll'; Vue.use(infiniteScroll); ``` 在模板中可以通过如下方式进行配置和绑定: ```html <div v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10"> </div> ``` - **`v-infinite-scroll="loadMore"`**: 绑定一个回调函数 `loadMore`,当满足条件时会调用此函数来加载更多的数据[^1]。 - **`:infinite-scroll-disabled="busy"`**: 控制是否禁用无限滚动的功能。如果变量 `busy` 的值为 `true`,则不会触发滚动加载逻辑[^4]。 - **`:infinite-scroll-distance="10"`**: 定义距离页面底部多远(单位为像素)时触发加载操作。 #### 常见问题及其解决方案 1. **重复加载** 如果发现存在多次连续调用 `loadMore` 函数的情况,可能是因为未正确设置 `infinite-scroll-disabled` 属性或者未能及时更新状态标志位。例如,在 `loadMore` 方法内部应该先将 `this.loading` 设置为 `true` 来阻止后续立即触发新的请求[^3]。 2. **加载无效** 当遇到无法正常触发加载行为的现象时,请确认以下几个方面: - 是否已经成功导入并初始化了 `vue-infinite-scroll` 插件; - HTML 结构中的容器是否有足够的高度以及正确的样式属性支持滚动检测 (比如设置了固定的高度与溢出隐藏)[^5]。 3. **与其他框架冲突** 对于某些特定场景下的 UI 库(如 Element Plus),单纯依赖其内置的 Infinite Scroll 功能可能会失效,则建议按照官方文档指引调整参数或尝试自定义处理机制[^2]。 ### 示例代码片段 下面给出一段完整的例子展示如何利用上述知识点完成基本的数据分页查询效果: ```javascript export default { data() { return { list: [], // 存储已获取的数据项集合 page: 1, // 记录当前处于第几页数,默认初始值设为1 totalPage: null, // 总共可翻阅的最大页码数量 busy: false // 表明现在是不是正忙于抓取新资料的状态标记符 }; }, methods:{ async loadMore(){ if(this.busy || this.page >= this.totalPage){ return; } try{ this.busy=true; const response=await fetch(`https://api.example.com/items?page=${this.page}`); const newData=response.json(); this.list.push(...newData.results); // 将新增加的内容追加至原数组末端 this.page++; // 更新计数值准备迎接下次读取动作的发生 }catch(error){ console.error('Error during fetching:', error.message); }finally{ this.busy=false; // 不管成功与否都要重新开放允许继续向下拉动刷新的机会窗口 } } } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值