本文参加了由公众号@若川视野 发起的每周源码共读活动,点击了解详情一起参与。
【若川视野 x 源码共读】第45期 | 分析 vant4 源码,如何用 vue3 + ts 开发一个瀑布流滚动加载的列表组件
点击了解本期详情一起参与。
今天阅读的是:vant4 源码中的列表组件
尝试使用
源码分析
克隆相关项目
首先,查看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');
来触发加载事件
- 如果触底,则通过
总结
我们通过学习vant
的list
组件,知道了怎么去使用vscode
调试代码。
通过对组件的分析和源码的解读,我们学到了一些hook
的使用和整个组件是怎么处理触底逻辑的。
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END