在线客服

【源码共读】- 分析 vant4 源码瀑布流滚动加载的列表

adminadmin 报建百科 2024-04-24 176 18
【源码共读】| 分析 vant4 源码瀑布流滚动加载的列表

本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。

【若川视野 x 源码共读】第45期 | 分析 vant4 源码,如何用 vue3 + ts 开发一个瀑布流滚动加载的列表组件
点击了解本期详情一起参与。

今天阅读的是:vant4 源码中的列表组件

github.com/youzan/vant…

尝试使用

源码分析

克隆相关项目
首先,查看package.json

  • 因为我们需要调试的是list组件,定位到该文件,打上断点
  • 配置debug 的 launch.json

  • 打开网页,进入断点


我们需要解决以下几个问题:

  • 怎么监听滚动到底部
  • 触发加载

我们来看下组件中做了哪些监听

// 省略...
// 监听 loading, finished, error 的变化,执行 check 方法
watch(() => [props.loading, props.finished, props.error], check);
// 省略...
const check = () => {
  nextTick(() => {
    if (
      loading.value ||
      props.finished ||
      props.disabled ||
      props.error ||
      // skip check when inside an inactive tab
      tabStatus?.value === false
    ) {
      return;
    }
    // 从props中获取 offset 和 direction
    const { offset, direction } = props;
    // 获取父级元素的位置信息
    const scrollParentRect = useRect(scrollParent);
    // 如果父级元素的高度为0 或者 root 元素隐藏,则不执行
    if (!scrollParentRect.height || isHidden(root)) {
      return;
    }
    // 获取占位元素的位置信息
    let isReachEdge = false;
    const placeholderRect = useRect(placeholder);

    if (direction === 'up') {
      isReachEdge = scrollParentRect.top - placeholderRect.top <= offset;
    } else {
      isReachEdge =
        placeholderRect.bottom - scrollParentRect.bottom <= offset;
    }
    // 触底了,就执行 load 方法
    if (isReachEdge) {
      loading.value = true;
      emit('update:loading', true);
      emit('load');
    }
  });
};

其中用到了useRect, useScrollParent, useEventListener,我们来看下源码怎么实现

// vant-use/src/useRect
import { Ref, unref } from 'vue';

const isWindow = (val: unknown): val is Window => val === window;

const makeDOMRect = (width: number, height: number) =>
({
  top: 0,
  left: 0,
  right: width,
  bottom: height,
  width,
  height,
} as DOMRect);

export const useRect = (
  elementOrRef: Element | Window | Ref<Element | Window | undefined>
) => {
  // 取元素本身
  const element = unref(elementOrRef);
  // 如果是window,直接返回innerWidth,innerHeight
  if (isWindow(element)) {
    const width = element.innerWidth;
    const height = element.innerHeight;
    return makeDOMRect(width, height);
  }
  // 否则使用getBoundingClientRect获取
  if (element?.getBoundingClientRect) {
    return element.getBoundingClientRect();
  }

  return makeDOMRect(0, 0);
};

// vant-use/src/useScrollParent

// 省略...
export function getScrollParent(
  el: Element,
  root: ScrollElement | undefined = defaultRoot
) {
  let node = el;
  // 遍历 el 的父节点,直到找到滚动元素
  while (node && node !== root && isElement(node)) {
    const { overflowY } = window.getComputedStyle(node);
    if (overflowScrollReg.test(overflowY)) {
      return node;
    }
    node = node.parentNode as Element;
  }
  // 如果不存在滚动元素,则返回根节点
  return root;
}

export function useScrollParent(
  el: Ref<Element | undefined>,
  root: ScrollElement | undefined = defaultRoot
) {
  const scrollParent = ref<Element | Window>();

  onMounted(() => {
    if (el.value) {
      scrollParent.value = getScrollParent(el.value, root);
    }
  });

  return scrollParent;
}

// vant-use/src/useEventListener
// 省略...
export function useEventListener(
  type: string,
  listener: EventListener,
  options: UseEventListenerOptions = {}
) {
  // 如果不是浏览器环境,直接返回
  if (!inBrowser) {
    return;
  }

  const { target = window, passive = false, capture = false } = options;

  let attached: boolean;

  const add = (target?: TargetRef) => {
    const element = unref(target);
    // 添加事件监听
    if (element && !attached) {
      element.addEventListener(type, listener, {
        capture,
        passive,
      });
      attached = true;
    }
  };

  const remove = (target?: TargetRef) => {
    const element = unref(target);
    // 移除事件监听
    if (element && attached) {
      element.removeEventListener(type, listener, capture);
      attached = false;
    }
  };
  // 组件卸载时  移除监听事件
  onUnmounted(() => remove(target));
  onDeactivated(() => remove(target));
  // 组件挂载时  添加监听事件
  onMountedOrActivated(() => add(target));

  if (isRef(target)) {
    watch(target, (val, oldVal) => {
      remove(oldVal);
      add(val);
    });
  }
}


总结一下,刚刚我们提出了问题

  • 怎么监听滚动到底部
    • 通过监听滚动元素事件,触发check函数,函数中对比占位元素和滚动元素的差值与offset比较,判断是否触底
  • 触发加载
    • 如果触底,则通过emit('load');来触发加载事件

总结

我们通过学习vantlist组件,知道了怎么去使用vscode调试代码。
通过对组件的分析和源码的解读,我们学到了一些hook的使用和整个组件是怎么处理触底逻辑的。

本网站是一个以CSS、JavaScript、Vue、HTML为核心的前端开发技术网站。我们致力于为广大前端开发者提供专业、全面、实用的前端开发知识和技术支持。 在本网站中,您可以学习到最新的前端开发技术,了解前端开发的最新趋势和最佳实践。我们提供丰富的教程和案例,让您可以快速掌握前端开发的核心技术和流程。 本网站还提供一系列实用的工具和插件,帮助您更加高效地进行前端开发工作。我们提供的工具和插件都经过精心设计和优化,可以帮助您节省时间和精力,提升开发效率。 除此之外,本网站还拥有一个活跃的社区,您可以在社区中与其他前端开发者交流技术、分享经验、解决问题。我们相信,社区的力量可以帮助您更好地成长和进步。 在本网站中,您可以找到您需要的一切前端开发资源,让您成为一名更加优秀的前端开发者。欢迎您加入我们的大家庭,一起探索前端开发的无限可能!
代办报建

本公司承接江浙沪报建代办施工许可证。
联系人:张经理,18321657689(微信同号)。

喜欢0发布评论

18条评论

  • 指尖站群 发表于 3个月前

    勤奋灌水,天天向上!http://www.baishanct.com/

  • 游客 发表于 2个月前

    楼主英明!http://essg.cqyiyou.net/test/344417693.html

  • 游客 发表于 2个月前

    林子大了,什么鸟都有了啊!http://test.cqyiyou.net/test/

  • 游客 发表于 2个月前

    最近回了很多帖子,都没人理我!http://mmmt.lnzskj.cn

  • 游客 发表于 17小时前

    楼主的头像能辟邪啊!http://www.guangcexing.net/tv/VhgUSWRpCea.html

发表评论

  • 昵称(必填)
  • 邮箱
  • 网址