라이브러리 없이 무한 스크롤 구현하기!!!
어떤 방식으로 구현할까 고민하다가 Intersection Observer API라는 좋은 기능을 찾았습니다.
Intersection Observer API는 상위 요소 또는 최상위 문서의 viewport와 대상 요소 사이의 변화를 비동기적으로 관찰할 수 있는 수단을 제공합니다.
기본 사용법
let options = {
root: document.querySelector("#scrollArea"),
rootMargin: "0px",
threshold: 1.0,
};
let observer = new IntersectionObserver(callback, options);
컬럼과 댓글을 불러오는데 모두 사용하기 위해 커스텀 훅으로 관리하도록 만들어 보겠습니다.
import { useEffect, useRef } from 'react';
function useIntersectionObserver(callback: IntersectionObserverCallback) {
// IntersectionObserver 인스턴스를 저장하는 ref
const observerRef = useRef<IntersectionObserver | null>(null);
// 관찰할 DOM 요소를 저장하는 ref
const elementRef = useRef<HTMLDivElement | null>(null);
useEffect(() => {
if (!elementRef.current) return;
observerRef.current = new IntersectionObserver(callback, {
// 관찰 기준을 현재 요소의 부모로 설정
root: elementRef.current.parentNode as Element,
threshold: 0.95,
});
observerRef.current.observe(elementRef.current);
// 컴포넌트 언마운트 시 Observer 연결 해제
return () => observerRef.current?.disconnect();
}, [callback]);
return elementRef;
}
export default useIntersectionObserver;
이제 준비는 끝났습니다. 컴포넌트에 적용해 봅시다!!
PC 2개로 확인해 본 결과 반응형까지 모두 완벽하게 동작합니다!!!
이렇게 끝났으면 좋았을 텐데... 팀원 중 한 분이 테스트하다가 문제가 생겼다고 연락을 주셨습니다.
이상이 없길 바라는 마음으로 혹시 몰라 노트북으로 확인해 보니...
일부 사이즈 화면에서 Intersection Observer API가 동작하지 않았습니다.
일단 문제가 될 수 있는 상황부터 차근차근 찾아보았습니다. 고려해 볼 상황은 아래와 같습니다.
- Viewport나 Root의 변경 감지 실패
- 문제 상황 : 브라우저나 디바이스 사이즈가 변경되었을 때, IntersectionObserver는 자동으로 다시 계산되지 않을 수 있습니다.
- 해결 방법 : resize 이벤트를 추가해서 브라우저 사이즈가 변경될 때마다 IntersectionObserver를 다시 생성하거나 갱신합니다.
- 적용 결과 : 실패...😥
- root 또는 target이 올바르게 설정되지 않음
- 문제 상황 : root 요소를 명시적으로 설정했다면, 디바이스 사이즈가 변경될 때 root 요소의 사이즈가 달라질 수 있습니다. 이로 인해 IntersectionObserver가 예상과 다르게 동작할 수 있습니다.
- 해결 방법 : root 요소의 사이즈가 변경될 때 옵저버를 갱신합니다.
const endRef = useIntersectionObserver(handleObserver, rootRef);
- 적용 결과 : 실패...😥
- 비동기적 렌더링으로 타이밍 문제가 발생
- 문제 상황 : API 요청으로 데이터를 받아와 DOM에 업데이트가 완료되기 전에 IntersectionObserver가 실행될 수 있습니다.
- 해결 방법 : DOM이 업데이트가 완료되면 Intersection Observer가 실행될 수 있도록 수정합니다.
const handleObserver = useCallback( ([entry]) => { if (entry.isIntersecting && columnData.cursorId && !isLoading) fetchCards(columnData.cursorId); }, [fetchCards, columnData.cursorId, isLoading], );
- 적용 결과 : 실패...😥
알아본 결과 내에서는 모두 해결하지 못했습니다...😭😭😭😭
멘토님께 피드백을 받은 결과 현업에서는 성능과 효율 측면에서 라이브러리를 사용하신다고 답변을 해주셨습니다.
라이브러리 없이 무한 스크롤을 구현해 봤다는 점에서 큰 성취감을 얻었습니다.
노트북을 제외한 대부분의 PC에서는 작동을 한다는 점에서는 만족하지만 모든 디바이스와 브라우저를 만족시키지 못한 점에서 아쉬움이 너무 커서 이 문제는 라이브러리가 어떤 방식으로 동작하는지 확인 후 수정하겠습니다🔥🔥🔥🔥
'프로젝트 > Next+TypeScript' 카테고리의 다른 글
[Taskify] 할 일 카드 모달 컴포넌트 (feat. optimistic update) (6) | 2024.12.20 |
---|---|
[Taskify] 무한스크롤 - 해결 (4) | 2024.12.19 |
[Taskify] 대시보드 상세 페이지 (4) | 2024.12.17 |
[Taskify] Chip(공통 컴포넌트) 추가 (4) | 2024.12.16 |
[Taskify] 버튼(공통 컴포넌트) 추가 (4) | 2024.12.13 |