인피니트 스크롤링

chu·2021년 5월 3일
0
post-thumbnail

2~3주간 진행했던 개인 프로젝트는 얼추 마무리가 되었다.
포트폴리오 사이트를 만드는 과정만 남았다. 또한
면접에 필요한 기술면접 또한 공부를 해야한다.

그 전에 어떤 프로젝트에 쓰인 코드를 정리를 하려고 한다. 기억보단 기록이 좋으니까


인피니트 스크롤링

웹, 모바일에서 빠질 수 없는 기법이지 아닐까 싶다. 말 그대로 무한 스크롤링인데 많은 데이터를
한번에 불러올 경우 로딩 시간이 길어져서 사용자에게 불편함을 느끼게 한다. 기획에 맞는 몇개의 데이터를 불러온 뒤 스크롤을 하면 새로운 데이터를 불러오는 기법이다.

Front

인피니트 스크롤링을 만드는데 있어서 스크롤 이벤트를 사용했다. 아마 처음 인피니트 스크롤링을 접할 때 가장 흔히 접하는 방법인 것 같다. 이도저도 말고 바로 코드를 기록하겠다.

// pages/index.tsx
import React, { useEffect } from 'react';
import { postsLoad } from '../actions/post';

const Home = () => {
  const { loadPostsMore, loadPostsLoading } = useSelect((state) => state.post)

  useEffect(() => {
    const infiniteScroll = throttle(() => {
      if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) {
        // loadPostMore true / loadPostsLoading false면 dispatch
        if (loadPostsMore && !loadPostsLoading) {
          dispatch(
            postsLoad({
              lastId,
            }),
          );
        }
      }
    }, 200);
    // 스크롤 이벤트 발생
    window.addEventListener('scroll', infiniteScroll);
    return () => {
      // 메모리 누수로 인한 스크롤 이벤트 지우기
      window.removeEventListener('scroll', infiniteScroll);
    };
  }, [loadPostsMore, loadPostsLoading, lastId, dispatch]);
}

(a) window.scrollY - 스크롤을 얼마나 내렸는지에 대한 값
(b) document.documentElement.clientHeight - 현재 화면에 보이는 높이 값
(c) document.documentElement.scrollHeight - 페이지의 총 높이 값

이 세가지를 사용해서 인피니트 스크롤링을 구현하였다.
간단하게 식을 풀자면, (a) + (b) > (c)-300 의 식이 된다.

(c)의 총 길이가 1000이고, 여기서 -300을 해준다. 그럼 (c)는 700이다.
그럼 (a) + (b)가 700이면 dispatch를 하게된다.

이런 방법으로 하게되면 원하는 위치에서 dispatch를 할 수 있다. 그냥 이렇게만 한다면 저 스크롤위치에서 엄청난 양의 dispatch를 하게된다. 당연히 성능 저하에 악영향을 끼치게 된다.

그래서 lodash 라이브러리의 throttle을 사용했다.

lodash

모듈성, 성능 및 기타 기능을 제공하는 최신 JavaScript 유틸리티 라이브러리. 라고 파파고에서
번역을 해주는데~ 자바스크립트로 사용되는 메소드를 lodash를 통해서 간편하게 사용할 수 있다.
공식문서 참고

그 중 사용 된 throttle은 콜백함수와 시간을 정할 수 있는데, 시간을 1000(1초)로 정해두고
스크롤을 하게된다면, 1초안에는 반드시 1번만 콜백함수를 실행한다. 이 기능을 사용해서 dispatch가
불필요하게 실행되는 부분을 방지했다.

마지막은 연속 두번을 불러온 것 같지만 제대로 불러온건 맞다..

스크롤로 인해서 실행되는 불필요한 디스패치를 방지했다고 끝은 아니다.
조건에 맞게 디스패치 해서 액션이 실행이 되야한다.

if (loadPostsMore && !loadPostsLoading) {
  dispatch(
    postsLoad({
      lastId,
    }),
  );
}

loadPostsMore - 포스트를 불러올 수 있는 상태
loadPostsLoading - 포스트를 불러오는 중

위 조건으로 진행을 했는데 loadPostsMore는 불러올 수 있는 포스트가 남아있다면 계속 true상태를 유지하게 된다.

loadPostsLoading는 포스트를 back서버로 요청해서 불러오는 중 pending 상태를 의미한다. 기본 상태를 true지만, 포스트를 불러올 경우 fulfilled가 되면 false로 상태 변경을 하기 때문에 두개의 조건이 충족되기 때문에 포스트를 불러오게 된다.

lastId는 어떻게 사용될까?

lasId는 현재 불러온 포스트들 중 마지막으로 불러온 포스트의 id값을 갖고 있다.
이 값을 back 서버에 요청할 때 쿼리스트링으로 보낸다. back 서버에서는 어떻게 진행되는지 알아보자.

Back

최초 로딩되어 아래 router가 실행되면, 쿼리스트링으로 0을 보내게된다. 이건 기본값으로 스크롤링을 하게되면 위에서 lastId의 값을 쿼리스트링으로 전달한다. 그럼 router에서는 쿼리스트링이 0보다 클 경우 스크롤링으로 간주하여, lastId보다 작은 id의 포스트를 불러온다.

// back/routes/posts
router.get('/', async (req, res, next) => {
  try {
    // 초기 조건은 없는 상태
    const where = {};
    
    // req.query가 0보다 클 경우 (즉, 스크롤링 시)에 where에 조건 추가
    if (parseInt(req.query.last, 10) > 0) {
      // 마지막 포스트의 id값 보다 작은 포스트를 불러오기
      where.id = { [Op.lt]: parseInt(req.query.last, 10) };
    }

    // 최초 로드일 때는 order 순으로 9개를 불러온다.
    // 추후 스크롤링 시에는 마지막 id보다 작은 포스트를 order순으로 9개를 불러온다.
    const post = await Post.findAll({
      where,
      order: [
        ['createdAt', 'DESC'],
      ],
      limit: 9,
      
      // ...
      
    });
    res.status(200).json(post);
  } catch(error) {
    console.error(error);
    next(error);
  }
});

이렇게 스크롤 이벤트를 사용하여 인피니트 스크롤링을 구현하였다.
스크롤 이벤트는 좋은 방법이 아니라고는 하는데... 시간이 많다면 더 나은 방법으로도 구현하고 싶다.

profile
한 걸음 한걸음 / 현재는 알고리즘 공부 중!

0개의 댓글

관련 채용 정보