스크롤이벤트를 attachment하여 현재 스크롤위치를 계산해서
맨 끝에 스크롤되었을 때 다음 데이터를 fetch하는 방법을 사용한다.
스크롤마다 한번의 이벤트가 발생하므로(거의 픽셀단위) 성능이슈를 예방하기 위해 쓰로틀링 적용을 고려해야한다.
스크롤방식처럼 1픽셀 1이벤트 + 쓰로틀링을 구현하지 않고 같은 기능을 구현할 수 있다.
위치를 계산하는건 동일하지만 사각형 영역을 계산해 요소가 어디 위치해있는지를 확인하는 방법으로 getBoundingClientRect()
메소드를 사용한다
Infinite Scroll에 적용해본다면, 특정요소가 viewport 에 존재하는지 판단해 데이터 fetch를 결정할 수 있다.
여기서 viewPort란 사용자가 보고있는 페이지의 영역이다.
따라서 getBoundingClientRect는, 현재 보고있는 영역내에 이벤트를 트리거할 요소가 존재하는지 확인하기 위한 위치연산 함수인 것이다.
그러나 이 과정에서 reflow가 발생하여 스크롤로 구현하는 방법과 마찬가지로 성능이슈가 생길 수 있다.
요소의 위치, 크기를 계산하고 위치시키는 reflow를 수행하지 않는 IntersectionObserver 를 사용함으로써 이러한 문제를 해결할 수 있다.
viewport와 특정 요소가 교차하는지를 관찰하여 특정 %이상겹쳐졌을 때 겹쳐졌음을 감지하는 기능을 제공하는 웹 API다.
사용자가 보고있는 영역이 빨간색이고 이벤트를 트리거할 요소가 하단의 InterSectRef라고 했을 때,
50%겹쳐졌을 때 이벤트를 트리거한다고 작성을 했다면 아래 그림의 시점에서 새로운 데이터를 불러오는 요청을 보내도록 구현할 수 있다.
매우 간단하게 !
먼저 초기화
어떤 요소를 계속 관찰해야하기 때문에 observer를 생성해주어야한다.
이 옵저버를 초기화할 때 options 인자를 전달하는데,
options은 특정요소와 viewport가 얼마만큼 겹쳤을 때 겹쳤다고 할지와 관찰대상의 부모요소를 지정하는 객체이다.
threshold가 바로 영역의 %를 뜻하는 프로퍼티이다. 0.5면, 50%겹쳐있을 때 콜백이 실행된다.
가장 중요한 것은 '무엇을' 관찰할지 인데 Js에서는 dom 선택을 사용해 할 수 있지만, 리액트는 load이전까지 html이 비어있으므로
useRef를 사용해 dom을 선택하도록 한다.
observe 메소드를 사용해 관찰할 target dom을 지정해서 영역 겹침 감지를 시작하도록 하자.
이를 코드로 구현하면 다음과 같다.
const options = {
root: null,
//기본 null, 관찰대상의 부모요소를 지정
rootMargin: "20px",
// 관찰하는 뷰포트의 마진 지정
threshold: 1.0,
// 관찰요소와 얼만큼 겹쳤을 때 콜백을 수행하도록 지정하는 요소
};
useEffect(() => {
const observer = new IntersectionObserver(handleObserver, options);
if (interSectRef.current){
observer.observe(interSectRef.current);
}
return () => observer.disconnect();
}, [handleObserver]);
interSectRef 요소를 관찰할 것이며 viewport와 1.0(100%) 겹쳐졌을 때 겹쳐짐이 true로 set될 것이다.
options로 초기화, target을 관찰하기 시작했다면 다음은 교차되었을 때 특정 행위를 수행할 콜백을 작성하는 것이다.
위의 코드에서 콜백은 handlerObserver 이며 이는entries라는 배열인자를 받는다.
IntersectionObserver은 비동기적으로 수행되므로 useEffect에서 clean up에 disconnect를 사용해 관찰을 중지시킬 수 있도록 한다.
observer는 하나 또는 여러 대상들을 관찰할 수 있는데 관찰타겟을 entries라는 배열로 받는다.
관찰대상을 여러개 지정했으면 각 요소의 정보들이 담겨있다. -> 무슨 태그고,, 어느위치에 있고 현재 겹쳐져있는지,,, 등등
observe의 대상이 intersectRef 하나이기에 0번째 요소가 관찰 대상이된다.
threshold가 한 방향 그리고 다른 방향으로 교차될 때 실행된다. (위에서 아래, 아래에서 위, 혹은 좌 -> 우, 우->좌 일때를 얘기하는 듯함)
const handleObserver = useCallback(async (entries) => {
const target = entries[0];
if (target.isIntersecting) {
console.log("is InterSecting");
setPage((prev) => prev + 1);
}
}, []);
target과 뷰포트가 1.0, 즉 100% 겹쳐졌을 때 isIntersecting이 true로 되고 if조건에 만족해 다음 페이지를 불러오도록 state값을 더했다.
useEffect(async () => {
await fetchData(page);
}, [page]);
교차될 때마다 page값을 증가시켜 useEffect가 호출되어 다음페이지에 대한 요청을 호출한다.
생각했던 것 보다 구현에 있어서 어려움이 크게 있진 않았다.
다만 발생할 수 있는 문제점(비동기함수 clean up)등을 잘 체크하며 구현하는 것이 중요하다고 생각된다..