(저는 간단한 DEMO버전이기때문에 APP.js에서 무한스크롤기능을 만들었습니다. 필요하면 다른 컴포넌트를 만들어서 해도 무방 합니다. 😃😃)
// src/components/App.js
import { useEffect, useState } from 'react';
import ReviewList from './ReviewList';
import { getReviews } from '../api';
const LIMIT = 6;
function App() {
const [order, setOrder] = useState('createdAt');
const [offset, setOffset] = useState(0);
const [hasNext, setHasNext] = useState(true);
const [items, setItems] = useState([]);
const [target, setTarget] = useState(null); // 구독할 대상 (target을 지켜보고 있다가 이 target이 정해진 threshold 비율만큼 보이면 지정한 행동을 합니다. )
const handleLoad = async (options) => {
const { paging, reviews } = await getReviews(options);
if (options.offset === 0) {
setItems(reviews);
} else {
setItems([...items, ...reviews]);
}
setOffset(options.offset + options.limit);
setHasNext(paging.hasNext);
};
useEffect(() => {
let options = {
threshold: "1", //타겟 엘리먼트가 교차영역에 진입했을 시점에 observer를 실행하는 것을 의미
};
// 새롭게 생성할 observer가 수행할 행동 정의
let handleIntersection = async ([entries], observer) => {
if (entries.isIntersecting) {
hasNext && await handleLoad({ order, offset: offset, limit: LIMIT });
observer.unobserve(entries.target);
}
};
// 새로운 observer 생성
const io = new IntersectionObserver(handleIntersection, options);
if (target) io.observe(target);
offset === 0 && handleLoad({ order, offset: 0, limit: LIMIT })
return () => io && io.disconnect();
}, [target, offset]);
return (
<div>
<ReviewList items={sortedItems} setTarget={setTarget} />
</div>
);
}
export default App;
아래 코드에 의거해서target설정을 가장 마직막의 DOM을 받아올수 있습니다. const lastItem = idx === items.length - 1;
// src/components/ReviewList.js
function ReviewList({ items, onDelete, setTarget }) {
return (
<ul>
{items.map((item, idx) => {
//* 새로 불어온 데이터 중 가장 마지막 값을 찾아 target으로 설정함 (마지막 데이터를 구독, 데이터를 새로 불러올 때마다 target이 바뀜) */
const lastItem = idx === items.length - 1;
return (
<li key={idx}>
<ReviewListItem item={item} onDelete={onDelete} ref={lastItem ? setTarget : null} /> // setTarget을 마지막 item일때 props로 전달
</li>
);
})}
</ul>
);
}
Cursor-based Pagination이 필수는 아닙니다. 아래와 같은 조건을 모두 만족한다면 Offset-based 을 사용해도 전혀 문제가 없습니다.
1. 중복데이터가 발생해도 상관 없는 경우 2. 전체 데이터 양이 적은 경우 3. 새로운 데이터 생성이 빈번하지 않은 경우 4. 검색엔진이 색인을 생성하지 않고, 임의의 사용자가 오래된 데이터를 조회하지 않는 경우
Cursor-based를 구현해야 하는 경우 백엔드에서 조금 더 공수가 들어간다. 정말 간단한 백오피스(관리자 페이지가 따로 있는 서비스) 성격의 서비스라면 Offset-based 방식이 나을 것 같다. 하지만 그 이외에 경우에는 처음부터 Cursor-based Pagination 방식으로 구현하는게 좋은 것 같다.