복지몰 프로젝트를 하면서 상품 리스트를 Scroll Event를 이용하여 무한스크롤을 구현하였다.
콘텐츠 전체 길이와 현재 스크롤 길이를 비교하여 스크롤 바닥을 감지하는 방법이다.
이 방식은 스크롤이 움직일 때마다 이벤트 핸들러가 호출되므로 반드시 Throttle이나 Debounce를 사용해서 이벤트를 빈도를 줄여줘야한다.
const 복지몰: React.FunctionComponent = () => {
const [visibleItems, setVisibleItems] = useState<number>(ITEMS_PER_PAGE); // 페이지당 보여줄 항목 수
const [isLoading, setIsLoading] = useState<boolean>(false);
// 스크롤 이벤트 핸들러
const handleScroll = () => {
if (isLoading) return;
const scrollHeight = document.documentElement.scrollHeight;
const currentHeight =
document.documentElement.scrollTop + window.innerHeight;
if (currentHeight + window.innerHeight >= scrollHeight - 140) {
setIsLoading(true);
setTimeout(() => {
setVisibleItems((pre) => pre + ITEMS_PER_PAGE);
setIsLoading(false);
}, 1000);
}
};
const debouncedHandleScroll = debounce(handleScroll, 100);
useEffect(() => {
window.addEventListener("scroll", debouncedHandleScroll);
return () => {
window.removeEventListener("scroll", debouncedHandleScroll); // 이전에 등록한 스크롤 이벤트 리스너 제거
};
}, [debouncedHandleScroll]);
return (
<Container>
<Title>오하우스 복지몰</Title>
<SubTitle>
오하우스 모든 직원들이 복지포인트를 이용하여
<br />
쇼핑을 할 수 있는 공간입니다
</SubTitle>
<ProductCardContainer>
{DummyData.slice(0, visibleItems).map(
(data: ProductModel.IProductModel, index: number) => {
return <복지몰_상품 key={data.id} data={data} index={index} />;
}
)}
</ProductCardContainer>
{isLoading && (
<>
{visibleItems >= DummyData.length
? null
: ( <div style={{ textAlign: "center", margin: "10px 0" }}>
<SyncLoader color={Color.Orange} />
</div>
)}
</>
)}
</Container>
);
};
ScrollHeight: 보이지 않는 화면까지 포함하여 전체 글의 길이
ScrollTop: 맨 처음부터 ~ 현재 화면에 보이는 부분까지의 길이
innerHeight: 창 틀을 뺀 화면의 높이
if (currentHeight + window.innerHeight >= scrollHeight - 140) {
setIsLoading(true);
setTimeout(() => {
setVisibleItems((pre) => pre + ITEMS_PER_PAGE);
setIsLoading(false);
}, 1000);
}
1) "currentHeight + window.innerHeight >= scrollHeight - 140"
현재 스크롤 위치와 전체 콘텐츠 높이를 비교하여 페이지 하단에 도달했다고 판단되어 if문을 실행한다.
2) setTimeout 수를 사용하여 1초 후에 코드를 실행하게 하였고
그 시간 동안 로딩 상태가 표시된다.
3) "setVisibleItems(visibleItems + ITEMS_PER_PAGE)"
새로운 콘텐츠를 화면에 보여주기 위해서 항목수를 증가 시키는데
상수로 6을 정의해서 6개씩 추가로 보여준다.
4) 마지막으로 콘텐츠를 성공적으로 로드하면 로딩상태를 false로 해제시켜준다.
const debouncedHandleScroll = debounce(handleScroll, 100);
useEffect(() => {
window.addEventListener("scroll", debouncedHandleScroll);
return () => {
window.removeEventListener("scroll", debouncedHandleScroll);
// 이전에 등록한 스크롤 이벤트 리스너 제거
};
}, [debouncedHandleScroll]);
2) useEffect를 사용하여 visibleItems의 상태 변수가 변할 때마다 실행되는데 스크롤 이벤트 리스너를 등록하고, 컴포넌트가 언마운트 될 때는 이벤트 리스너를 제거해준다.
<참고>
페이지네이션을 사용하면 사용자가 특정 페이지로 이동하여 원하는 데이터를 빠르게 찾을 수 있어 사용자의 편의성을 향상시켜준다.
보통 페이지네이션은 어드민, 게시판에서 많이 사용 하였다.
무한 스크롤은 사용자가 페이지 끝에 도달했을 때 새로운 항목이 자동으로 로드되어 추가 스크롤 없이도 계속해서 컨텐츠를 볼 수 있다.
사용자는 페이지를 새로고침하거나 페이지 클릭 없이도 이동할 수 있으니 더 편리하다고 볼 수 있다.
또한 새로운 컨텐츠가 계속해서 로드되기 때문에 더 많은 제품을 보고 구매를 할 수 있다.
보통 사용자들이 페이지에 많이 머무는 사이트에서 무한스크롤을 많이 사용하는 것 같다.
debounce: 해당 이벤트 그룹이 일시정지 되었다고 판단되는 시점이 지나면 최초 또는 최후의 이벤트에 대해서만 처리하는 방식
ex) 입력이 끝난 후 api를 호출한다.
Throttle: 이벤트를 일정 주기마다 처리함으로써 이벤트를 제어하는 방식이다. Throttle의 설정시간으로 1ms를 주게 되면 해당 이벤트는 1ms 동안 최대 한 번만 발생하게 된다.
ex) 500ms마다 api를 호출한다.