제일 간단하게 할 수 있는 것은, window에게 scroll event를 거는 방법이다. 비교적 간단하게 할 수 있지만, 스로틀(throttle) 함수를 적용해주지 않으면 브라우저의 성능에 굉장히 좋지 않다. (엄청나게 많은 요청이 간다.)
요소의 좌표를 얻는 getBoundingClientRect 함수로 scroll event를 구현할 수도 있다. 하지만 이 방식은 Reflow를 일으켜서 좋은 방법은 아니다.
Intersection Observer API는 타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법입니다. - MDN
무한스크롤 외에도 다양하게 사용할 수 있는 API같은데, 나에게는 많이 어려웠다. 다양하게 응용해봐야 익숙해질 것 같다.
마지막 요소에 닿을 때, 데이터를 요청해서 list에 push하는 형태로 구현한다.
무한 스크롤 구현을 위해 기본적으로 있어야할 것들이 있다.
function App() {
const [page, setPage] = useState(1); // 페이지 번호를 위해 필요
const [list, setList] = useState(null); // 데이터를 받아와서 저장
const [element, setElement] = useState(null); // 요소의 관찰을 위해 필요
const URL = '~~';
데이터를 요청하고, 렌더링 하기위한 함수를 선언한다.
const fetchData = async (page) => {
const result = await axios.get(
`${URL}?_page=${page}&_limit=10`
);
// 데이터를 가져오고 난 뒤, 데이터를 추가한다.
setList((prev) => {
if (!prev) return result.data;
else return [...prev, ...result.data];
});
};
// ...
}
map을 이용해 Comments 컴포넌트를 렌더링한다.
return (
<Wrapper>
{list &&
list.map((comment, index) => {
return (
<div ref={setElement} key={index}>
{/* 마지막 div가 element에 담긴다. */}
<Comments
id={comment.id}
email={comment.email}
body={comment.body}
/>
</div>
);
})}
</Wrapper>
);
// observer를 만들어서 계속 이용한다.
// IntersectionObserver라는 Web API를 이용
const observer = useRef(
new IntersectionObserver(
(entries) => {
const first = entries[0];
// isIntersecting의 값이 true일 때는 page 값을 더한다.
// isIntersecting은 관찰로 인해서 true 혹은 false가 되는 값이다.
if (first.isIntersecting) {
setPage((prev) => prev + 1);
}
},
{ threshold: 1 }
)
);
// page 값이 늘어날 때마다 데이터를 가져온다.
useEffect(() => {
fetchData(page);
}, [page]);
useEffect(() => {
// element가 바뀔때마다 동작한다.
const currentElement = element;
const currentObserver = observer.current;
if (currentElement) {
currentObserver.observe(currentElement);
}
return () => {
if (currentElement) {
currentObserver.unobserve(currentElement);
}
};
}, [element]);
이해가 잘 안가서 콘솔에 찍어보았다.
1) 처음 접속시 currentElement는 마지막 요소(여기서는 10번째 div)로 저장된다.
2) 스크롤을 내리다가 맨 밑 div에 닿으면 데이터를 요청한다.
3) 10번째 div는 관찰이 종료된다. (unobserve)
4) currentElement는 20번째 div가 된다.