하옹의 프론트앤드 이야기 - Infinite Scroll(무한 스크롤)
Throttle과 Debounce에 대한 설명
react-intersection-observer로 무한스크롤 구현하기
둘 모두 연이어 발생하는 이벤트에 의해 무의미한 리소스 낭비가 일어나지 않게끔 방지하는 기법이다.
var throttler;
window.onscroll = () => {
// throttle
if(!throttler) {
throttler = setTimeout(() => {
throttler = null; console.log('throttle');
}, 200);
}
}
출처: https://canoe726.tistory.com/22 [All-in-one]
var debouncer;
document.querySelector('.search').addEventListener('input', e => {
// debounce
if(debouncer) {
clearTimeout(debouncer);
}
debouncer = setTimeout(() => {
console.log('debounce');
}, 500);
});
출처: https://canoe726.tistory.com/22 [All-in-one]
scroll event
: DOM scroll Event를 이용하는 것이라 상대적으로 구현은 쉽지만 throttle
이나 rAF
로 최적화를 해줘야 한다.IntersectionObserver
: 마지막 요소가 사용자에게 보이면 다음 데이터를 불러오는 방법.react의 react-intersection-observer
라이브러리를 사용하면 IntersectionObserver 기반의 무한 스크롤을 쉽게 구현할 수 있다.
import React from "react"
import { useInView } from "react-intersection-observer"
const App = () => {
const [ref, inView] = useInView()
return (
<div ref={ref}>
Element {inView.toString()}
</div>
)
}
export default App
ref
를 div에 걸어주면 해당 요소가 보이면 inView가 true
로, 안 보이면 false
로 자동으로 변경된다.
라이브러리를 쓰지 않는 데에 의의를 두며 우리 프로젝트의 무산스크롤 구현 방법으로 최종적으로 scroll event 기반 방법을 선택했다. 또한 fetch를 적게 받아오기 위해 캐싱도 적용했다.
infiniteScroll Custom Hook
: ref.addEventListener("scroll", scrollEvent);
의 scrollEvent에서 일정시간마다 scroll의 길이가 Math.abs(ret - scrollHeight) <= 50
인지 확인하고 만약 그러하다면 isSetting
을 true
로 바꾼다.infiniteScroll Custom Hook
: useEffect(()=>{}, \[isSetting\])
에서 isSetting이 true라면 fetchCallback을 호출
한다.infiniteScroll Custom Hook의 부모(page/index.tsx)
: 우리 프로젝트의 경우 1번의 fetchCallback 함수는 page/index.tsx의 getImgUrl
이란 함수인데,infiniteScroll Custom Hook의 부모(page/index.tsx)
: 1번의 fetch 함수가 호출되었을 때 더는 slice할 데이터가 없다면 infiniteScroll Custom Hook의 isDone을 true로 바꿔준다.infiniteScroll Custom Hook
: infiniteScroll Custom Hook에선 isDone의 값이 바뀌어 리랜더링되며 scroll 이벤트 리스너를 해제한다.위의 4. 이벤트리스너 삭제
에서 removeEventListener가 잘 수행되지 않는데, 이벤트 함수를 선언한 위치의 문제였다. scrollEvent를 다음과 같이 useEffect 안에 선언하니 원하는 대로 동작했다.
useEffect(() => {
if (!ref) return;
prev = 0;
const scrollEvent = () => {
throttle(handleScroll, 100);
};
if (!isDone) ref.addEventListener("scroll", scrollEvent);
return () => {
ref.removeEventListener("scroll", scrollEvent);
};
}, [ref, isDone]);