Skip to main content

Snippet - Intersection Observer API custom hook

当需要 React 中使用 Intersection Observer 來實現無限滾動時,可以創建一個 custom Hook。

創建 Custom Hook

import { useCallback, useState } from "react";

interface IntersectionOptions {
root?: Element | null;
rootMargin?: string;
threshold?: number | number[];
}

interface UseOnScreenHook {
measureRef: (node: Element | null) => void;
isIntersecting: boolean;
observer: IntersectionObserver | undefined;
}

const useOnScreen = ({
root = null,
rootMargin = "0px",
threshold = 0,
}: IntersectionOptions = {}): UseOnScreenHook => {
const [observer, setObserver] = useState<IntersectionObserver | undefined>();
// 目標元素是否在視窗可見
const [isIntersecting, setIntersecting] = useState(false);

const measureRef = useCallback(
(node: Element | null) => {
if (node) {
const intersectionObserver = new IntersectionObserver(
([entry]) => {
setIntersecting(entry.isIntersecting);
},
// 初始化 Intersection Observer
// root 表示根節點,預設是 null,表示用瀏覽器視窗作為根節點
// rootMargin 表示在根的邊界周圍加上margin,預設是0px
// threshold 表示元素可見度的闕值,介於 0 至 1 之間,預設為0,表示只要元素任何部分進入視窗就被視為可見(若設為1則全部進入才算可見)
{ root, rootMargin, threshold }
);

intersectionObserver.observe(node);
setObserver(intersectionObserver);
}
},
[root, rootMargin, threshold]
);

return { measureRef, isIntersecting, observer };
};

在元件中使用方式如下

const Component = () => {
const [hasMore, setHasMore] = useState(true);
const { measureRef, isIntersecting, observer } = useOnScreen();

const fetchData = useCallback(async (token: string) => {
setIsLoading(true);
try {
// CALL API 拿到資料,並進行資料處理

//倘若沒有資料則設setHasMore為false
setHasMore(false);
} catch (error) {
throw error;
} finally {
setIsLoading(false);
}
}, []);

useEffect(() => {
if (isIntersecting && hasMore && !isLoading) {
fetchData();
if (observer) {
observer.disconnect();
}
}
}, [isIntersecting, hasMore]);

return <div ref={measureRef}></div>;
};

這個元件使用 useOnScreen 來創建一個 measureRef,並將它綁定到一個 <div> 元素。當這個 <div> 元素進入視口時,isIntersecting 會變為 true,觸發 useEffect 中的條件判斷,進而觸發 fetchData() 來加載更多數據。

參考資料