티스토리 뷰

React

무한스크롤 만들기 참쉽죠...

변기원 2022. 4. 19. 03:51

일단 스크롤 컴포넌트만 만들고 나면 로직은 참 쉽다.

단, 내가 한 것보다 분명히 더 쉬운 방법이 있을 것 같긴 하다 ㅋㅋ

일단 만들어둔 Scroll컴포넌트로 list.map()을 감싸준다.

callNext는 미리 정해둔 위치가 왔을때 실행되는 함수,

is_next는 다음페이지가 있는지 없는지 판단하는 불리언(다음 페이지가 없을 때는 스피너를 안 띄울 예정)

loading은 비동기 처리중에 같은 이벤트가 두 번 발생하지 않도록 막아주는 불리언

 

저번 주 미니 프로젝트를 할 때 페이징 처리를 하면서 알았는데 서버에서 보내주는 데이터를 살펴보면 서버에서

각 페이지 별로 뿌려줄 게시물 수, 총 게시물 수, 총 페이지 수 등을 알 수 있고, 내가 페이지 번호만 보내면 그 번호에 맞는 게시물을 뿌려주는 게 가능하다는 것..

그럼 처음 게시물 불러올 땐 1페이지를 불러오는 것이나 마찬가지고

내가 스크롤을 천천히 내려서 한번 무한스크롤이 동작하면 2페이지를 불러오는 거나 마찬가지고

또 스크롤을 천천히 내리면 3페이지를 불러오는 것이나 마찬가지고.

단, 그걸 post.list에 덮어씌워서 대체하는 것이 아니라, push()를 써서 목록에 붙여주는 것만 주의하면 거의 페이징 처리와 다를 바가 없다.

 

그러므로 나에게 필요한 것은 

내가 스크롤 동작이 1번 진행된 상황이면 현재 페이지를 2라고 인식할 수 있어야 하고, 2번 진행된 상황이면 현재 페이지가 3이라고 알 수 있어야 한다.

나는 처음에 메인화면을 켜면 useEffect로 첫 게시물을 불러올 땐 getFirstPostDB라는 미들웨어를 사용하고

두 번째 이상부터는 getNextPostDB를 사용한다.

(왜 이렇게 나눴는지 솔직히 지금 포스팅할 땐 기억이 잘 안 나는데,, 같은 미들웨어를 사용했을 때 한 번에 1페이지가 두 번 불려 오는 상황이 있었던 것 같다.. 코드를 만들 땐 일단 되게 하자!라는 생각밖에 없어서ㅜㅜ)

이게 처음 작동하는 getFirstPostDB인데, 나는 어차피 무조건 1페이지만 부를 것이므로 page에 1을 고정시켰다. 

다른 페이지는 부를 생각이 없다!

미들웨어가 시작하자마자 loading을 true로 바꿔서 비동기 미들웨어가 두 번 동작하지 않도록 해주자.

그리고 response에는 우리 백엔드 팀원들이 보내준 1페이지 게시물의 정보가 들어있을 것이다.

나는 방금 1페이지를 조회했으니 paging이라는 변수를 만들어서 그 안에 키 벨류 형태로 

다음 스크롤에 불러올 페이지인 start, 그다음에 불러올 페이지인 next를 설정했다.

굳이 그 다음다음의 페이지까지 지정한 이유는 내가 2페이지를 불러올 때, 3페이지가 있는지 없는지 미리 알 수 있으면 스피너를 띄울지 말지 선택할 수 있기 때문이다. 즉, 위 상황이라면 2페이지 로딩하는 순간 3페이지가 없으면 서버 측에 뭔가 불리언 값을 요구할 생각이었다.

근데 실제로 response를 찍어서 모양을 보니 data.last에 해당 페이지가 마지막인지 나타내는 불리언이 이미 있네?

우리 팀원들이 나를 위해 보내준 것일까? 어쨌든 이걸 활용하면 굳이 저 next는 필요가 없다.

그 불리언을 활용하기 위해 paging에 lastPage라는 키를 만들어서 넣어줬다. 

지금은 1페이지가 끝이 아니기 때문에 저기에는 false가 들어갈 것이다.

리듀서에 들어오면 list에 response로 받은 post_list를 넣어주고!

paging에는 방금 업데이트 한 다음 paging 정보를 넣어주고!

이제 다 끝났으니까 is_loading은 false로 만들어준다!

 

여기까지 하면 화면에는 첫 게시물이 뿌려질 것이고, paging에 start는 2, lastPage는 false가 되어 있을 것이다.

이제 다음 스크롤부터는 여기서 실행이 된다. callNext는 내가 Scroll컴포넌트에서 정해놓은 조건이 되면 실행이 된다.

나는 바닥에서 100px 남으면 실행이 되도록 해놨다.

스크롤을 계속 내려서 마지막 100px이 되면 getNextPostDB가 실행된다. 함수의 인자를 보면 paging.start를 가지고 들어간 것을 알 수 있다. 즉, 2를 가져갔을 것이다.

이번에는 page에 2가 들어왔을 것이다. 역시 미들웨어가 중복 실행되는 것을 막기 위해 loading부터 true로 만들어주고

2페이지에 해당하는 response를 받는다!

그리고 역시 다음 스크롤도 준비해야 되니까 paging을 업데이트한다. 인자로 받은 page에 +1을 계속해줄 것이고

lastPage도 계속 업데이트해준다. 만약 지금 2페이지가 마지막 페이지였다면 여기서 lastPage가 true가 된다.

역시 next는 쓸모가 없다. 지워야지...

이 정보를 가지고 getNextPost로 가면

이번에는 list에 push()를 해줘야 한다. 왜냐하면 원래 있던 게시물 밑에 붙일 거니까.. 

그리고 페이징 정보도 업데이트된 페이징으로 바꿔주고(이제 여기서 lastPage가 true가 들어가겠지?)

모든 과정이 끝나고 게시물이 붙었을 테니 is_loading도 false로 바꾼다.

 

자 그럼 이제 리덕스 state를 보면 paging에 lastPage가 true로 바뀌어 있다. 

지금 우린 2페이지에 해당하는 게시물도 아래쪽에 붙여놨지만

3페이지는 없는 상태다.

여기서 is_next에 false가 들어와야 로딩이 멈추게 되어있으니까

lastPage가 true일 땐 false로, false일 땐 true로 입력하도록 해줬다.

이제 아무리 내려도 3페이지는 없으며,  3페이지를 부르는 스피너도 동작하지 않을 것이다.

유저는 뭔가 더 로딩될 것이라도 착각할 일도 없게 되었다.

 

이렇게 해서 무한 스크롤까지 구현을 해봤습니다!

 

아래는 저와 같은 초심자 개발자 분들이 참고하실 수 있는 Scroll 컴포넌트 코드입니다.

단, 복붙을 해서 쓰시더라도 throttle과 debounce에 대해서는 알고 계셔야 하며

이 기회에 useCallback도 공부해보면 좋을 것 같습니다!

import React from "react";
import _ from "lodash";
import Spinner from "./Spinner";
import { useCallback } from "react";

const Scroll = (props) => {
  const { children, callNext, is_next, loading } = props;
  const _handleScroll = _.throttle(() => {
    if (loading) {
      return;
    }

    const { innerHeight } = window;
    const { scrollHeight } = document.body;
    const scrollTop =
      (document.documentElement && document.documentElement.scrollTop) ||
      document.body.scrollTop;
    if (scrollHeight - innerHeight - scrollTop < 100) {
      callNext();
    }
  }, 500);

  const handleScroll = React.useCallback(_handleScroll, [loading]);

  React.useEffect(() => {
    if (loading) {
      return;
    }
    if (is_next) {
      window.addEventListener("scroll", handleScroll);
    } else {
      window.removeEventListener("scroll", handleScroll);
    }

    return () => window.removeEventListener("scroll", handleScroll);
  }, [is_next, loading]);

  return (
    <React.Fragment>
      {props.children}
      {is_next && <Spinner />}
    </React.Fragment>
  );
};
Scroll.defaultProps = {
  children: null,
  callNext: () => {},
  is_next: false,
  loading: false,
};

export default Scroll;

 

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG more
«   2025/05   »
1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28 29 30 31
글 보관함