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

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

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

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

github.com/youzan/vant…

尝试使用

list.gif

源码分析

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

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

image.png

  • 打开网页,进入断点

image.png


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

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

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

// 省略...
// 监听 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的使用和整个组件是怎么处理触底逻辑的。

© 版权声明
THE END
喜欢就支持一下吧
点赞0

Warning: mysqli_query(): (HY000/3): Error writing file '/tmp/MYeXJtIK' (Errcode: 28 - No space left on device) in /www/wwwroot/583.cn/wp-includes/class-wpdb.php on line 2345
admin的头像-五八三
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

图形验证码
取消
昵称代码图片