[개인 프로젝트] Trablog - 1. 무한 스크롤(InfiniteScroll)을 어떻게 구현하지?

Point Check·2021년 6월 7일
0
post-thumbnail

🔁무한스크롤?

처음에는 그냥 홈에서 모든 포스트를 불러와서 그대로 보여주는걸 선택했다. 그래서 그냥 GraphQL Resolver도 getPosts를 하나 만들어서 몽땅 전체 포스트를 불러오기로 막연하게 만들었다. 그러나 조금만 생각해보니 너무 많으면 초기 로딩이 오래 걸릴 것 같아서 조금만 불러오고 다시 가져오는 방법을 선택했다.

우선 백엔드(TypeORM) 먼저

처음엔 이렇게 코드를 작성했다.

const postList = await this.posts.find({ relations: ['user'] });

이걸 다음과 같이 수정했다.

//take는 몇개를 가져올건지, skip은 몇개를 건너뛸지
const postList = await this.posts.find({
        relations: ['user'],
        take: 9,
        skip: skipFrom,
        order: { createdAt: 'DESC' },
      });

조금만 더 복잡한게 나오면 QueryBuilder를 써야할지도 모르겠다.

프론트는?

여기서 본격적인 문제를 직면한다.

scroll Event를 쓸까?

그래, eventListner를 달아줘서 scroll할때마다 페이지의 끝에 도달했는지를 확인해보자.
그런데 스크롤 할때마다 느껴지는 버벅임은 기분 탓일 수도 있다.
이때 생각난건 전에 Image를 Lazy loading 하게 할때 쓴 Intersection Observer 가 생각이 났다.

Intersection Observer

Intersection Observer 는 특정 element가 보일때 이를 감지하여 비동기적으로 특정한 함수를 실행시킬때 쓴다.

Intersection Observer를 이용하기 위해 커스텀 훅을 만들었다.

export default function useObserver(onWork: Function) {
  const targetElement = useRef<HTMLDivElement>(null);
  //감시할 Element
  const observer = useRef<IntersectionObserver>();
  //옵저버 객체

  const onEnd = (entries: IntersectionObserverEntry[]) => {
    entries.forEach(async (entry) => {
      if (entry.isIntersecting) {
        onWork();
        //보여졌을 때 실행할 함수
       	//위에서 받아온 함수를 실행 시킨당.
      }
    });
  };

  const setObserver = () => {
    if (!observer.current) {
      observer.current = new IntersectionObserver(onEnd);
    }
    targetElement.current && observer.current?.observe(targetElement.current);
  };
  const unsetObserver = () => {
    targetElement.current && observer.current?.unobserve(targetElement.current);
  };

  return [targetElement, setObserver, unsetObserver] as [
    typeof targetElement,
    typeof setObserver,
    typeof unsetObserver
  ];
  //감시할 Element와 Observe를 활성화, 비활성화 시키는 함수를 반환한다.
}

이를 어떻게 활용했냐면

Post들을 보여주는 Container 최하단에 빈 div하나를 넣어주고 이를 targetElement로 지정한 후 추가로 로딩함 여부를 결정하는 state를 하나 만들어

  const [loading, setLoading] = useState<boolean>(false);
  const [targetElement, setObserve, unsetObserver] = useObserver(() => {
    setLoading(true);
  });
  
  useEffect(() => {
    if (loading) {
      (async () => {
        //대충 api요청하는 내용
        setLoading((loading) => !loading);
      })();
    }
  }, [loading]);

이런 식으로 구현하였다.

0개의 댓글