전 세계 어느 사이트를 가던, 사용자에게 최적의 경험을 제공하기 위해 필요한 기능이 있다.
바로 페이지네이션(Pagination)이다.
이름에서 알 수 있듯이 페이지네이션은 사용자가 데이터를 보기 편하게 한 화면에 정해진 양의 데이터를 보여주는 기능을 말한다.
어느 사이트를 가던 위 그림과 같이 정보를 분할해놓는 구조를 다들 본적이 있을 것이다.
같은 기능이지만 사용자가 더 쉽고 편하게 정보에 접근할 수 있게 구현된 방법으로 Infinite scrolling이 있다.
페이스북이나 인스타그램, 유튜브, 핀터레스트 등 많은 사이트가 위의 방식을 사용하고 있다.
페이지네이션 기능을 구현하기 위해서는 백엔드와 프론트엔드에서 모두 작업해야 하며, 프론트엔드는 백엔드에 표시할 데이터의 위치와 표시할 데이터의 개수를 요청해야 하며 백엔드에서는 요청에 맞는 데이터를 응답한다. 페이지네이션을 어떻게 구현하는지에 따라서 현재 데이터의 위치를 표현하는 방법이 다르다.
- Offset-based
오프셋: 표시할 데이터 위치(=몇 번째 페이지인지), Limit: 표시할 최대 데이터 개수- Cursor-based
커서: 첫번째로 표시할 데이터의 위치(=현재 커서 위치), Limit: 표시할 최대 데이터 개수
이번 프로젝트에서 사용한 방법은 오프셋 기반 Infinite scrolling이다.
유튜브 영상을 참고하여 많은 도움이 되었다.
Ref라는 개념을 처음 다루게 되는데, Ref는 DOM을 직접 조작하는데 사용된다
1. 특정 input에 포커스 주기
2. 스크롤 박스 조작하기
3. Canvas 요소에 그림그리기
등등...
이번 프로젝트에서는 2번, 스크롤 조작을 Ref를 통해 관리하게 된다.
function App() {
const [query, setQuery] = useState("");
const [pageNum, setPageNum] = useState(1);
const { loading, error, list, hasMore } = useSearchBook(query, pageNum);
const observer = useRef();
const lastBookElementRef = useCallback(
(node) => {
if (isLoading) return;
if (observer.current) observer.current.disconnect();
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) { // 마지막 요소가 보이고 더 로딩할 페이지가 존재하면
setPageNum((prev) => prev + 1); // 다음 페이지를 불러온다
}
});
if (node) observer.current.observe(node);
},
[loading, hasMore]
);
const handleChange = (e) => {
setQuery(e.target.value);
setPageNum(1);
};
return (
<div className="App">
<h1>Infinite Scroll</h1>
<input type="text" onChange={handleChange} value={query} />
{list.map((item, i) => {
const isLastElement = books.length === i + 1;
isLastElement ? (
<div key={i} ref={lastBookElementRef}>
{book}
</div>
) : (
<div key={i}>{book}</div>
)
})}
<div>{isLoading && "Loading..."}</div>
<div>{error && "Error..."}</div>
</div>
);
export default App;