為了方便,我今天找了一段時間,找到一個不用api key的免費Api,並且可以在後面更換postId,我們就可以假設他是像page的東西,他每一個postId會給我5個Comment, 我們可以利用這個做出無限輪動去增加他postId來抓取更多資料
https://jsonplaceholder.typicode.com/comments?postId=1
我的習慣是先寫hook,所以我們就先來寫hook吧
首先可以先拿上面api試試看,可以看到他的型別長這樣
type CommentType = {
postId: number;
id: number;
name: string;
email: string;
body: string;
};
接下來我們先來寫一個簡單的抓api function 並且存到state中,這應該不會太困難,記得我們[]要放進PostId,這樣我們增加或改變它的時候才能再次抓api
const [comments, setComments] = useState<CommentType[]>([]);
const [postId, setPostId] = useState(1);
const [loading, setLoading] = useState(false);
useEffect(() => {
async function fetchComments() {
try {
setLoading(true);
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com/comments?postId=${postId}`
);
setComments((prev) => [...prev, ...data]);
} catch (err) {
console.log(err);
} finally {
setLoading(false);
}
}
fetchComments();
}, [postId]);
接下來是比較困難的了,如果不知道intersectionObserve API的,可以回去看之前那篇,裡面有莫力全大神講解的,我這邊就講解怎麼寫比較好
const observer = useRef<IntersectionObserver | null>(null);
這裡我們使用了 useRef
來創建一個**ref
,這個ref
**將持有我們的 IntersectionObserver
實例。初始值是 null
,表示當前沒有 observer。
const intersectioningRef = useCallback(
useCallback
是一個 React Hook,它返回一個 memoized 的 callback。這意味著它不會在每次渲染時重新創建,只有當它的依賴改變時才會重新創建。
(node) => {
這是回調函數的主體。當你的 React component 的某個 DOM 元素被掛載或更新時,這個函數將被調用,並接收這個 DOM 元素作為參數 node
。
if (loading) return;
如果當前正在**loading
**,就退出函數,不要做任何事情。
if (observer.current) {
檢查我們的 observer ref
是否有值。如果有,這表示我們之前已經創建了一個 IntersectionObserver
實例。
observer.current.disconnect();
如果有之前的 IntersectionObserver
實例,我們需要先中斷它的觀察。這是為了確保在掛載新的元素或更新元素時,之前的觀察不會影響新的觀察。
observer.current = new IntersectionObserver((entries) => {
創建一個新的 IntersectionObserver
實例,並將其賦值給我們的 observer
ref。
if (entries[0].isIntersecting) {
當被觀察的元素(node
)進入或離開 viewport 時,這個回調將被調用。entries
是一個陣列,包含所有被觀察的元素的信息。在這裡,我們只關心第一個元素。
setPostId((prev) => prev + 1);
如果被觀察的元素進入了 viewport,則增加 postId
的值,代表我們已經到最底了。
if (node) observer.current.observe(node);
如果 node
存在(即不是 null
或 undefined
),則開始使用我們的 IntersectionObserver
來觀察它。
const observer = useRef<IntersectionObserver | null>(null);
const intersectioningRef = useCallback(
(node) => {
if (loading) return;
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
setPostId((prev) => prev + 1);
}
});
if (node) observer.current.observe(node);
},
[loading]
);
完整程式碼:
import axios from "axios";
import { useCallback, useEffect, useRef, useState } from "react";
type CommentType = {
postId: number;
id: number;
name: string;
email: string;
body: string;
};
export default function useGetComments() {
const [comments, setComments] = useState<CommentType[]>([]);
const [postId, setPostId] = useState(1);
const [loading, setLoading] = useState(false);
useEffect(() => {
async function fetchComments() {
try {
setLoading(true);
const { data } = await axios.get(
`https://jsonplaceholder.typicode.com/comments?postId=${postId}`
);
setComments((prev) => [...prev, ...data]);
} catch (err) {
console.log(err);
} finally {
setLoading(false);
}
}
fetchComments();
}, [postId]);
const observer = useRef<IntersectionObserver | null>(null);
const intersectioningRef = useCallback(
(node) => {
if (loading) return;
if (observer.current) {
observer.current.disconnect();
}
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting) {
setPostId((prev) => prev + 1);
}
});
if (node) observer.current.observe(node);
},
[loading]
);
return { loading, comments, intersectioningRef };
}
接下來要寫component了,這裡就比較簡單
我們只要在最後一個元素放進監聽與他底下loading就好,所以用comments.length - 1 === index
判斷,這就是hook好的地方,讓整個component變得很乾淨,很簡潔,UI與邏輯分開
import * as React from 'react'
import useGetComments from './useGetComments'
export default function IntersectionObserverExample() {
const { loading, comments, intersectioningRef } = useGetComments()
return (
<div>
{comments.map((data, index) => {
if (comments.length - 1 === index) {
return (
<div ref={intersectioningRef}>
<p style={{ fontSize: '16px', fontWeight: 600 }}>現在的postId: {data.postId}</p>
<p style={{ fontSize: '14px', fontWeight: 500, color: 'blue' }}>我的Id: {data.id}</p>
<p style={{ fontSize: '14px', fontWeight: 500, color: 'blue' }}>我的name: {data.name}</p>
<p style={{ fontSize: '10px' }}>我的email: {data.email}</p>
<p style={{ fontSize: '8px' }}>我的body: {data.body}</p>
{loading ? <p style={{ fontSize: '30px', color: 'red' }}>loading...</p> : null}
</div>
)
}
return (
<div>
<p style={{ fontSize: '16px', fontWeight: 600 }}>現在的postId: {data.postId}</p>
<p style={{ fontSize: '14px', fontWeight: 500, color: 'blue' }}>我的Id: {data.id}</p>
<p style={{ fontSize: '14px', fontWeight: 500, color: 'blue' }}>我的name: {data.name}</p>
<p style={{ fontSize: '10px' }}>我的email: {data.email}</p>
<p style={{ fontSize: '8px' }}>我的body: {data.body}</p>
</div>
)
})}
</div>
)
}