사용자의 scroll event를 계속 보며 페이지가 끝에 오는지를 판단하는 방법을 구현이다. 하지만
드르륵 스크롤 할. 때마다 이벤트가 발생하는 것이다.
크롬에다 이것만 쳐봐도 알 수 있다.
window.addEventListener('scroll',()=>console.log('🌀'))
그렇다면 남은 것은
타겟과 viewPort 사이의 intersection 변화를 비동기적으로 관찰하는 방법
등에 사용된다.
여기서 나는 Infinite-scroll을 만들며 API를 겪어보았다.
스크롤할 대상을 만든 후
Intersection observer를 만들어서 ref로 끝나는 지점에 붙여줄 것이다!
현재 data는 10개씩 API query 요청하여 불러오고 있다.
파일은 크게 두가지
- useFetch(커스텀 fetch Hook)
- CardList(스크롤할 component)
CardContainer 가장 아래에 빈 div를 생성해 ref를 달아준다.
이곳에서 교차시점을 알아볼 것이기 때문
<CardListContainer>
{list?.map((card) => (
<Cardd>{card.id}</Cardd>
))}
<div ref={observer} />
<>{isLoading && <Loading />}</>
</CardListContainer>
참고) options는 옵션사항이라 예시코드에서는 따로 넣지 않았다.
let options = {
root: document.querySelector('#scrollArea'),
rootMargin: '0px',
threshold: 1.0
}
let observer = new IntersectionObserver(callback, options);
root
: 스크롤 할 대상을 감싼 요소 , 스크롤 할 곳
null 혹은 default = viewPort
rootMargin
: root 가 가진 여백
문자열로 넣기
threshold
: target이 root와 몇% 교차했을 때 callback 을 실행할 지 결정하는 값
0.0~1.0
pageNum
:페이지번호를 query 로 API 요청해 data를 받아오기 때문에 pageNum을 useFetch에 넘겨줌
list
: 뿌려줄 data
isLoading
: 로딩중인지 boolean
hasMore
: data 더 가져올 게 남았는지
< CardList.jsx >
const CardList = () => {
const [pageNum, setPageNum] = useState(1);
const { list, hasMore, isLoading } = useFetch(pageNum);
const observerRef = useRef();
const observer = (node) => {
if (isLoading) return;
if (observerRef.current) observerRef.current.disconnect();
observerRef.current = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting && hasMore) {
setPageNum((page) => page + 1);
}
});
node && observerRef.current.observe(node);
};
return (
<CardListContainer>
{list?.map((card) => (
<Cardd>{card.id}</Cardd>
))}
<div ref={observer}/>
<>{isLoading && <Loading />}</>
</CardListContainer>
);
};
export default CardList;
메인이 되는 부분만 주석을 달아 설명하자면
const observerRef = useRef();
const observer = (node) => {
// 1.로딩중이면 return
if (isLoading) return;
//onserverRef.current가 있으면 끊기
if (observerRef.current) observerRef.current.disconnect();
//observerRef.current 생성
observerRef.current = new IntersectionObserver(([entry]) => {
// entry 가 교차되었으며, 데이터가 더 있으면 페이지 1증가
if (entry.isIntersecting && hasMore) {
setPageNum((page) => page + 1);
}
});
//
node && observerRef.current.observe(node);
};
< useFetch.jsx>
import React, { useState, useEffect, useCallback } from 'react';
const END_POINT = 'https://';
const useFetch = (page) => {
const [list, setList] = useState([]);
const [hasMore, setHasMore] = useState(false);
const [isLoading, setIsLoading] = useState(false); //로딩 구현 시에만 필요
//query API 요청 보내기
const sendQuery = useCallback(async () => {
const URL = `${END_POINT}?${page}~~~`;
try {
setIsLoading(true);
const response = await (await fetch(URL)).json();
if (!response) {
throw new Error(`서버에 오류가 있습니다.`);
}
setList((prev) => [...new Set([...prev, ...response])]);
setHasMore(response.length > 0);
setIsLoading(false);
} catch (e) {
throw new Error(`오류입니다. ${e.message}`);
}
}, [page]);
useEffect(() => {
sendQuery();
}, [sendQuery, page]);
return { hasMore, list, isLoading };
};
export default useFetch;
// list. data 전에 있던 값 + 새로 가져온 query API 응답값을 더해 새로운 배열을 만들어 set
setList((prev) => [...new Set([...prev, ...response])]);
//응답값이 있으면 set
setHasMore(response.length > 0);
// data 불러오기 전 잠시 로딩중
setIsLoading(true);
// 중략
setIsLoading(false);
✨ 여기서 보너스
React 에서 component가 렌더링 될 때마다 내부에 선언된 표현식도 다시 선언되는데, 이것은 매우 낭비
이것을 방지하기 위해 useCallback을 사용할수 잇는데
1째 마운트 될 때 한번만 선언하고 그 이후로 재사용하자!
이벤트 핸들러 함수
api를 요청하는 함수에 주로 사용한다.
명심하자, 그래 메모리가 다돈이다~
참고 블로그1
참고 블로그2
참고 블로그3
좋은 블로그
🔗 Tami 다른 블로그
https://rrecoder.tistory.com/
오~ 스크롤을 내리다가 추가할 시점이 되면 페이지를 하나 추가해 주는 거군요 !