✨ 들어가기
리액트로 Pic.me를 개발하면서 가로 & 세로 무한스크롤을 개발해야 하는 뷰가 생성되었다.
리액트에서 무한 스크롤을 구현하는 방법은 2가지가 있다.
- scroll event를 감지
- Intersection Observer API
- 라이브러리 사용
우선 직접 scroll event를 감지하는 것은 해당 이벤트가 발생할 때마다 scrollHeight와 scrollTop + offsetHeight를 비교하면서 페이지의 끝에 도달했는지를 계산해야 한다.
하지만, 말에서도 알 수 있듯이 scroll 이벤트가 발생할 때마다 와 같은 과한 이벤트는 성능저하를 야기할 가능성이 높다.
때문에 나는 Intersection Observer API 를 통해 무한스크롤을 구현하기로 했다. 더불어 이를 활용한 라이브러리를 함께 이해하며 서비스에 도입해보고자 한다.
차트를 통해 최근에 가장 업데이트가 잘 되어있고, Intersection Observer API를 활용한 react-intersection-observer를 선택하였다.
✅ Intersection Observer API
Intersection Observer API는 타겟 요소와 상위 요소 또는
최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법
Target element가 Viewport 안에 보여지고 있는지를 관찰하는 방법이다.
💡 구체적으로 알아보기
👇 아래의 구문으로 IntersctionObserver 인스턴스를 만들어 낸다.
const observer = new IntersectionObserver(callback, options);
해당 API가 가지고 있는 주요 메서드를 살펴보자.
📍주요 메서드
- observe(tergetElement)
타켓 요소에 대한 관찰을 시작할 때 사용 - unobserve(targetElement)
타켓 요소에 대한 관찰을 멈출 때 사용 - disconnect( )
다수의 요소를 관찰하고 있을 때, 모든 관찰을 멈추고 싶을 때 사용 - takeRecords()
관찰 중인 요소들의 IntersectionObserverEntry 객체를 배열로 반환
📍 옵션 속성
Observer를 조정할 수 있는 속성이며, options 에 기입되는 내용들이다.
- root
대상 요소를 감지할 상위 요소, default로 null 값을 가진다. - rootMargin
바깥 여백을 사용해 root의 범위를 확장 또는 축소, default로 0px를 가진다. - threshold
관측 대상이 화면에 어느 정도 노출될 때 보인다고 감지하는 지에 대한 비율
0.0 ~ 1.0 의 범위이며, 0.0 은 1px이라도 보이면, 1.0은 전부 보일 때 노출을 감지 , default 값은 0.0이다.
✅ react-intersection-observer
해당 라이브러리를 통한 무한스크롤 구현은 생각보다 간단하다.
우선 설치를 진행하자.
yarn add react-intersection-observer
npm install react-intersection-observer --save
해당 라이브러리에서 제공되는 useInView의 props를 살펴보자.
📍 useInView로 전달가능한 props 들
- root
- rootMargin
- threshold
- onChange
inview의 상태가 변화될 때마다 실행되는 콜백함수 - skip
IntersectionObserver를 생성하는 것을 스킵, 기본값은 false이다. - triggerOnce
observer가 딱 한번만 실행, 기본값은 false이다. - initialInView
inView의 초깃값을 설정, 만약 처음부터 element가 보일 것으로 예상된다면 해당 속성에 true를 준다. 기본값은 false이다. - fallbackInView
만약 IntersectionObserver API가 실행되지 않고 에러가 발생할 경우, inView 값을 설정할 수 있다. 기본값은 false이다.
위의 props들은 아래 코드처럼 옵션으로 적용하면 된다.
import React from "react"
import { useInView } from "react-intersection-observer"
const Component = () => {
const { ref, inView, entry } = useInView({
/* Optional options */
threshold: 0,
})
return (
<div ref={ref}>
<h2>{`Header inside viewport ${inView}.`}</h2>
</div>
)
}
📍ref, inView로 무한 스크롤 구현하기
데이터를 끝까지 불러온 지를 확인하기 위한 div를 만들고 해당 요소에 ref를 연결해준다. 스크롤을 진행하면 해당 div가 viewport로 진입하면서 inView 값이 true가 된다.
여기서 useEffect를 통해 inView 값이 변화될 때, 새로운 list를 불러오는 함수를 실행하도록 하면 다음 데이터를 가져오게 된다.
이후 ref가 새롭게 연결되고, 연결된 div가 다시 viewport 밖으로 벗어나게 되면서 inView값이 false로 변경된다.
결과적으로 inView가 true가 아니기에 useEffect 내부의 setSize 함수는 실행되지 않는다.
👇 최종 구현 코드이다.
import React, { useCallback, useEffect } from 'react';
import { useInView } from 'react-intersection-observer';
const VoteList = () => {
const { ref, inView } = useInView({
threshold: 0.5,
});
const resetStickerInfoState = useResetRecoilState(stickerResultState);
// ✅ useSWRInfinite 를 사용하여 list 가져오기
const { voteListResult, isLoading, isError, size, setSize } = useGetCurrentVoteList();
const { userInfo } = useGetUserData();
const getMoreItem = useCallback(() => {
if (voteListResult && voteListResult.result) {
setSize((prev) => prev + 1);
}
}, [voteListResult, setSize]);
useEffect(() => {
resetStickerInfoState();
}, []);
useEffect(() => {
// ✅ inView 가 true 일때 리스트를 업데이트
if (inView && voteListResult?.result) {
getMoreItem();
}
}, [inView]);
if (isError) return <Error404 />;
if (isLoading) return <LandingVoteList />;
return (
<>
<StCurrentVote>현재 진행중인 투표</StCurrentVote>
{voteListResult?.result?.length ? (
<StVoteListWrapper>
{voteListResult.result.map((data, i) => (
<div key={i} ref={ref}>
<VoteCard voteData={data} />
</div>
))}
</StVoteListWrapper>
) : (
<StEmptyView>
<IcEmpty />
<p>{userInfo?.userName}님만의</p>
<p>투표를 만들어보세요!</p>
</StEmptyView>
)}
</>
);
};
export default VoteList;
🌈 마무리
스크롤을 직접 계산하고 page를 지정하여 무한 스크롤을 구현하는 것은 까다로운 부분들이 많다.
react-intersection-observer를 통해 쉽고 간편하면서 서비스에 맞는 UI/UX로 기능을 구현할 수 있었다.
📚 참고
'WEB > React' 카테고리의 다른 글
[ React ] useQuery 와 useMuataion를 활용한 기능개선 (0) | 2023.11.30 |
---|---|
[ 모던 리액트 : Deep Dive ] - 리액트 개발에서 반드시 알아야하는 JS (0) | 2023.11.28 |
[ 모던 리액트 : Deep Dive ] - 리액트에 대해 (1) | 2023.11.13 |
PWA + Safari (0) | 2023.07.03 |
Recoil (0) | 2022.07.03 |