今天,我決心調整學習的方向,證明給阿狗兄看,我並不是只學皮毛。我決定深挖 useInfiniteScroll,這個功能不只是無限滾動那麼簡單,它蘊含著無限延伸的哲理。
useInfiniteScroll
的實現原理基於持續監視滾動行為。當滾動達到特定條件(如接近元素邊界)時,便會觸發載入更多內容。
先把原始碼拆成以下幾個點,方便大家好理解:
讓我們逐一詳細解釋這些原理:
useInfiniteScroll
使用 useScroll
來監聽指定元素的滾動狀態。主要關注元素是否滾動到了指定方向(如底部)的邊緣。
const state = reactive(useScroll(element, {...}))
這允許函式實時追踪滾動位置,並在達到特定條件時觸發載入資料的行為。
使用 useElementVisibility
來檢測被監聽的元素是否在可是範圍內。
const isElementVisible = useElementVisibility(observedElement)
這確保了只有當元素可見時才會觸發載入,避免不必要的載入操作。
函式使用 ref
和 computed
來管理載入狀態:
const promise = ref<any>()
const isLoading = computed(() => !!promise.value)
這允許外部組件輕鬆追踪當前是否正在載入新內容。
當滿足載入條件時,函式會非同步執行 onLoadMore
回調:
promise.value = Promise.all([
onLoadMore(state),
new Promise(resolve => setTimeout(resolve, interval)),
])
使用 Promise.all
確保了載入操作和最小間隔都被遵守 (這個技巧在處理動畫載入時很重要。如果動畫只閃現一秒就消失,會給人一種突兀的感覺。)。
throttle
(通過設定 interval
)來限制載入頻率。nextTick
來延遲檢查,避免在同一個渲染週期內多次觸發載入。nextTick(() => checkAndLoad())
useScroll
更新滾動狀態。checkAndLoad
函式。checkAndLoad
檢查是否滿足載入條件(到達邊緣、元素可見、可以載入更多)。onLoadMore
回調。知道大概的原理後,我們來看看原始碼吧
type InfiniteScrollElement = HTMLElement | SVGElement | Window | Document | null | undefined
export interface UseInfiniteScrollOptions<T extends InfiniteScrollElement = InfiniteScrollElement> extends UseScrollOptions {
distance?: number
direction?: 'top' | 'bottom' | 'left' | 'right'
interval?: number
canLoadMore?: (el: T) => boolean
}
這裡定義了幾個重要的型別:
InfiniteScrollElement
:可以進行無限滾動的元素類型。UseInfiniteScrollOptions
:設定無限滾動行為的選項介面。useInfiniteScroll
函式export function useInfiniteScroll<T extends InfiniteScrollElement>(
element: MaybeRefOrGetter<T>,
onLoadMore: (state: UnwrapNestedRefs<ReturnType<typeof useScroll>>) => Awaitable<void>,
options: UseInfiniteScrollOptions<T> = {},
) {
// ... 函式主體
}
這是 useInfiniteScroll
的主要函式,它接受三個參數:
element
:要監聽滾動的元素。onLoadMore
:當需要載入更多內容時調用的回調函式。options
:控制無限滾動行為的選項。const {
direction = 'bottom',
interval = 100,
canLoadMore = () => true,
} = options
這裡從 options
中解構出需要的選項,並設定預設值:
direction
:滾動方向,預設為 'bottom'。interval
:兩次載入之間的最小間隔,預設為 100ms。canLoadMore
:判斷是否可以載入更多的函式,預設總是返回 true。const state = reactive(useScroll(element, {
...options,
offset: {
[direction]: options.distance ?? 0,
...options.offset,
},
}))
const promise = ref<any>()
const isLoading = computed(() => !!promise.value)
這裡使用 useScroll
來管理滾動狀態,並創建了 promise
和 isLoading
來追踪載入狀態。
const observedElement = computed<HTMLElement | SVGElement | null | undefined>(() => {
return resolveElement(toValue(element))
})
const isElementVisible = useElementVisibility(observedElement)
這段程式碼用於檢查被監聽的元素是否可見。
function checkAndLoad() {
state.measure()
if (!observedElement.value || !isElementVisible.value || !canLoadMore(observedElement.value as T))
return
const { scrollHeight, clientHeight, scrollWidth, clientWidth } = observedElement.value as HTMLElement
const isNarrower = (direction === 'bottom' || direction === 'top')
? scrollHeight <= clientHeight
: scrollWidth <= clientWidth
if (state.arrivedState[direction] || isNarrower) {
if (!promise.value) {
promise.value = Promise.all([
onLoadMore(state),
new Promise(resolve => setTimeout(resolve, interval)),
])
.finally(() => {
promise.value = null
nextTick(() => checkAndLoad())
})
}
}
}
這是 useInfiniteScroll
的核心邏輯:
onLoadMore
並設置一個最小間隔。promise
並在下一個 tick 再次檢查。const stop = watch(
() => [state.arrivedState[direction], isElementVisible.value],
checkAndLoad,
{ immediate: true },
)
tryOnUnmounted(stop)
這裡設置了一個監聽器,當滾動到達指定方向或元素可見性變化時,觸發 checkAndLoad
。同時,在組件卸載時停止監聽。
return {
isLoading,
reset() {
nextTick(() => checkAndLoad())
},
}
函式返回一個物件,包含:
isLoading
:表示當前是否正在載入的計算屬性。reset
:一個重置函式,用於強制檢查並可能觸發新的載入。useInfiniteScroll
結合了 Vue 的響應式系統和 DOM 操作。透過持續監控滾動狀態和元素可見性,在適當的時機載入更多內容。
本身的邏輯上還結合了多個 VueUse 的其他功能(如 useScroll
和 useElementVisibility
),同時處理了非同步操作和效能最佳化。算是一個很好的例子,示範了如何將多個簡單的功能組合成一個好用的工具。
今天就到這啦~如果有任遺漏或是錯誤再麻煩留言讓我知道
話說我這兩天跑去拔智齒要痛死了