굳이 Intersection Observer API를 사용하여 무한 스크롤을 구현하는 이유는?
타겟 요소와 상위 요소 또는 최상위 document 의 viewport 사이의 intersection 내의 변화를 비동기적으로 관찰하는 방법(출처 : MDN)
=> 이게 무슨 말일까? 우리가 정한 target 요소와 viewport가 교차하는 부분의 변화를 관찰한다. 즉, 관찰 중인 요소가 사용자가 보는 화면에 보이는 지 확인할 때 쓰는 API
new IntersectionObserver(callback, options)
옵션에는 root
, rootMargin
, threshold
가 있는데 보통 threshold만 사용한다. threshold: 0.7
이면 target이 화면에 70% 이상 보이기 시작할 때 콜백함수를 호출한다.
- useRef()로 target element 설정
- Observer에서 감지된 target를 포착해 getMoreItem() 실행
- 비동기 요청을 보낸다(나는 원래 있던 요소를 복사할 것이므로 그냥 1.5초 대기)
- 로딩 시작 -> 데이터 추가 성공 -> 로딩 종료
observer가 관찰할 target 요소를 설정한다.
return (
<div>
...
<div ref={setTarget}>{isLoaded && <Loading />}</div>
</div>
);
target이 화면에 70%이상 보이면 onIntersect 콜백함수를 실행한다.
useEffect(() => {
let observer;
if (target) {
observer = new IntersectionObserver(onIntersect, {
threshold: 0.7,
});
observer.observe(target);
}
return () => observer && observer.disconnect();
}, [target]);
target element가 화면에 보였을 때 새로운 데이터 추가 후 마지막 요소를 다시 관찰한다.
const [isLoaded, setIsLoaded] = useState(false);
const onIntersect = async ([entry], observer) => {
if (entry.isIntersecting && !isLoaded) {
observer.unobserve(entry.target); // 기존 관찰하던 요소는 관찰하지 않음
await getMoreItem(); // 새로운 데이터 추가
observer.observe(entry.target); // 새로운 데이터 마지막 요소 관찰
}
};
서버에 비동기 요청을 보내 데이터를 받아와서 state에 추가한다. 나는 임의로 갖고 있는 데이터를 복사하여 비동기 요청을 기다리는 것처럼 1.5초를 대기 후 데이터를 추가하였다.
const getMoreItem = async () => {
setIsLoaded(true);
await new Promise((resolve) => setTimeout(resolve, 1500));
const items = recommendItems.map((item) => {
return { ...item, id: nextId.current++ };
});
setRecommendItems((recommendItems) => recommendItems.concat(items));
setIsLoaded(false);
};
로딩중일 때 다음과 같이 보이게 될 화면의 윤곽을 미리 보여주는 애니메이션이다.
유튜브에서 자주 볼 수 있는데 스피너보다 사용자 친화적이고 이탈율을 줄일 수 있다.
react-loading-skeleton
라이브러리를 이용해 skeleton UI를 구현하였다.
<Skeleton />
컴포넌트로 쉽게 구현할 수 있고 필요한 요소를 라이브러리 내부의 속성들을 이용하거나 css를 적용시켜 원하는 모양을 만들 수 있다.
또한 skeleton.css
를 적용하면 로딩중일 때 애니메이션이 적용된다. 라이브러리를 자세히 알고 싶다면 아래 링크에 자세히 설명되어 있다.
react-loading-skeleton
import Skeleton from "react-loading-skeleton";
import "react-loading-skeleton/dist/skeleton.css";
import styles from "./SkeletonElement.module.scss";
const SkeletonElement = () => {
return (
<div>
<div>
<Skeleton className={styles.avatar} />
<div className={styles.info}>
<Skeleton width={"200px"} />
<Skeleton width={"200px"} />
</div>
</div>
</div>
);
};
export default SkeletonElement;
MDN Intersection Observer
intersectionObserver 무한스크롤 구현하기
무한스크롤 구현하기
무한 스크롤 구현하는 4가지 방법