어떤 기능인가?📌
스크롤을 일정부분 내리게 되면 더많은 데이터 및 정보가 밑으로 쌓이게 된다.
Infinite Scrolling 즉, 무한스크롤이란 계속 스크롤을 내리면서 정보를 탐색할 수 있는 기능을 말한다.
어떻게 구현 했을까? 🔑
무한스크롤을 구현하기위해 구글에 검색한 결과 크게 두가지 방법을 사용하였다.
scoll event와 IntersectionObserver API를 사용하는것! 📝
document.documentElement
에 접근하면 html geometry값에 접근이 가능하다.
geometry값을 알기쉽게 정리해놓은 이미지이다. ( 출처:javascript.info)
우선 내가 사용할 스크롤 이벤트는 가로축이 필요없다.
그러므로 세로축과 관련된
clientHeight, offsetHeight, scrollHeight, scrollTop, offsetTo을 모두 console.log()
로 찍어 보았다.
offsetHeigth , offset 은 고정값이므로 제외, 나머지들을 살펴보면
clientHeight는 컨텐츠의 높이이고 scrollTop은 최상단과 컨텐츠의 거리이고
scrollHeight은 전체 높이이다.
clientHeight + scrollTop === scrollHeight이라면 스크롤이 최하단에 위치한다는 것을 의미한다.🛎
그렇다면 위와같은 조건이 충족 만족되었을 때 state를 변경하여 쿼리 파라미터에 넣어주면 끝
const [page, setPage] = useState(1);
const [items, setItems] = useState([]);
const FetchData = () => {
// 쿼리 파라미터 사용하고 적절한 곳에 state를 넣어준다.
const url = `https://jsonplaceholder.typicode.com/comments?_page=${page}&_limit=10`;
fetch(url)
.then((res) => res.json())
// 기존 배열에 새로 패치한 데이터를 추가한다.
.then((item) => setItems((prev) => [...prev, ...item]));
};
useEffect(() => {
window.addEventListener("scroll", onScroll);
return () => {
window.removeEventListener("scroll", onScroll);
};
}, []);
const onScroll = () => {
const scrollTop = document.documentElement.scrollTop;
const clientHeight = document.documentElement.clientHeight;
const scrollHeight = document.documentElement.scrollHeight;
if (scrollTop + clientHeight === scrollHeight) {
// 조건 충족시 page 추가
setPage((prev) => prev + 1);
}
scroll 이벤트로 무한스크롤을 구현하게 될 때 단점은 한번 스크롤을 할 때마다 수많은
console.log()가 찍히는걸 볼 수 있다.
IntersectionObserver API👍
Intersection observer을 통해 설정한 요소의 교차점을 관찰 할 수 있고 요소가 뷰포트에 포함되는지 , 사용자 화면에 지금 보이는 요소인지 구별하는 기능을 제공한다.
이 기능은 비동기적으로 실행되기 때문에, scroll 같은 이벤트 기반의 요소 관찰에서 발생하는 렌더링 성능이나 이벤트 연속 호출 같은 문제 없이 사용할 수 있다.
우선 scroll Event 보다는 좀더 난이도가 있었다.
요소의 가시성을 고려하고, 교차점을 관찰 해야 했으며 Intersection observer API의 콜백함수 의 인자인 entries안에 다양한 방법이 숨어있다. 그 방법들은 위의 참고자료안에 있다.
나는 그중에서 isIntersecting: 관찰 대상의 교차 상태(Boolean)를 선택하였다.
관찰 대상이 루트 요소와 교차 상태로 들어가거나(true) 교차 상태에서 나가는지(false) 여부를 나타내는 값(Boolean)이다.
교차상태로 요소를 지목해야하기 때문에 useRef
를 사용하였다.
이후 설명은 코드를 보면서 진행한다.
const [feedData, setFeedData] = useState([]);
const [pageNumber, setPageNumber] = useState(1);
const [loading, setLoading] = useState(false);
const pageEnd = useRef();
const fetchFeeds = async (pageNumber) => {
const res = await fetch(
`https://jsonplaceholder.typicode.com/comments?_page=${pageNumber}&_limit=10`
);
const data = await res.json();
// 새로운 데이터를 기존 배열에 넣기
setFeedData((prev) => [...prev, ...data]);
// loading true를 통해 옵져버를 컨트롤한다.
setLoading(true);
};
useEffect(() => {
fetchFeeds(pageNumber);
}, [pageNumber]);
const loadMore = () => {
setPageNumber((prevPageNumber) => prevPageNumber + 1);
};
useEffect(() => {
//useRef로 지정한 요소가 아니면 패스!
if (!pageEnd.current) {
return;
}
if (loading) {
//옵저버를 쓰는 방법
const observer = new IntersectionObserver(
(entries) => {
//entries의 첫번째 요소의 isIntersecting를 확인한다. 궁금하면 console.log(entries)!
if (entries[0].isIntersecting) {
// 기존 쿼리값에서 +1해주는 함수
loadMore();
}
},
//옵저버가 실행되기 위해 타겟의 가시성이 얼마나 필요한지 백분율로 표시한다. 1은 100%이다.
{ threshold: 1 }
);
//관찰대상 정하기
observer.observe(pageEnd.current);
// 옵저버 연결해제
return () => observer && observer.disconnect();
}
}, [loading]);
내가 원하는 순간에 효율적으로 작동하는 무한스크롤을 사용하기위해서는 IntersectionObserver API를 사용하면 된다!😎