Pagination & Infinite Scroll

sohyeon kim·2022년 3월 31일
0

React & Javascript

목록 보기
22/41

페이지 처리를 하는 방법에는 크게 일반적인 방식과 무한스크롤 방식, 2가지 방법이 있다. 각각의 방법에 대해서 알아보자.

페이지네이션 (Pagination)

페이지 번호를 클릭해서 이동하는 방식의 페이지 처리 방법

페이지네이션은 콘텐츠를 웹사이트의 또 다른 페이지들로 분리하는 방법이다. 사용자는 페이지 하단에 있는 숫자 형식의 링크를 클릭하여 페이지들을 탐색할 수 있고, 페이지네이션된 콘텐츠 일반적으로 몇 가지 공통된 주제 혹은 목적들을 지니고 있다.

페이지 넘버는 일일히 숫자를 써주는 것이 아닌 Array, map, index를 사용해 만든다.

1) 기본 페이지네이션

import { useQuery, gql } from "@apollo/client";
import styled from "@emotion/styled";

// Page를 받기위해 variables 로 넣어준다.
const FETCH_BOARDS = gql`
  query fetchBoards($page: Int) {
    fetchBoards(page: $page) {
      _id
      writer
      title
      contents
    }
  }
`;

const MyRow = styled.div`
  display: flex;
  flex-direction: row;
`;
const MyColumn = styled.div`
  /* width: 25%; */
`;

export default function MapBoardPage() {
  const { data, refetch } = useQuery(FETCH_BOARDS);

  const onClickPage = (event) => {
    // 객체 형태로 refetch 안에 바인딩해준다. event.target.id; 텍스트라 숫자 Number로 바꿔주기
    refetch({ page: Number(event.target.id) });
    // event.target.id 실재 우리가 어떤 것을 클릭했는지 알 수 있는 부분
  };

  return (
    <div>
      {data?.fetchBoards.map((el) => (
        <MyRow key={el._id}>
          <MyColumn>{el.writer}</MyColumn>
          <MyColumn>{el.title}</MyColumn>
        </MyRow>
      ))}

      {/* 📌 방법 3 */}
      {/* new Array(10) : 비어있는 배열 10개 생성됨*/}
      {/* new Array(10).fill(아무숫자): fill 을 해서 10에 "아무숫자"를 채워줌 */}
      {/* 1,2,3,4,5,6,7,8,9,10 을 채우고 싶다면? */}
      {/* 비어있는 배열 10개만 만들어도 index+1을 해주면 된다.*/}
      {new Array(10).fill(1).map((_, index) => (
        <span key={index + 1} onClick={onClickPage} id={String(index + 1)}>
          {index + 1}
        </span>
      ))}

      {/* 📌 방법 2 */}
      {/* 자바스크립트를 작성하려면 {} 안에 써주기 */}
      {/* 방법 1과 같은 형식 다만 [] 안에 쓰는 것  */}
      {/* {[1, 2, 3, 4, 5, 6, 7].map((el) => (
        <span key={el} onClick={onClickPage} id={String(el)}>
          {el}
        </span>
      ))} */}

      {/* 📌 방법 1 */}
      {/* 만약 100 페이지까지 있으면 어떻게 할 것인가? 그리고 이것들을 하나하나 하드코딩 할 것인가? 아래들을 합칠 방법은? 다음 이전 페이지는? */}
      {/* <span onClick={onClickPage} id="1">
        1
      </span>
      <span onClick={onClickPage} id="2">
        2
      </span>
      <span onClick={onClickPage} id="3">
        3
      </span> */}
    </div>
  );
}

2) 이전페이지 다음페이지 만들어주기

import { useQuery, gql } from "@apollo/client";
import styled from "@emotion/styled";
import { useState } from "react";

// 게시글 몇번 보여줘가 아니라 전체 보여주니까 넘버 빼도 됨
const FETCH_BOARDS = gql`
  query fetchBoards($page: Int) {
    fetchBoards(page: $page) {
      _id
      writer
      title
      contents
    }
  }
`;

const MyRow = styled.div`
  display: flex;
  flex-direction: row;
`;
const MyColumn = styled.div`
  /* width: 25%; */
`;

export default function MapBoardPage() {
  const { data, refetch } = useQuery(FETCH_BOARDS);
  // 기준을 시작페이지 '1'로 잡는다
  const [startPage, setStartPage] = useState(1);

  const onClickPage = (event) => {
    refetch({ page: Number(event.target.id) });
  };

  // 이전페이지
  // 기준 페이지에 - 10
  const onClickPrevPage = () => {
    // 기존에 - 10
    setStartPage((prev) => prev - 10);
  };

  // 다음페이지
  // 기준 페이지에 + 10
  const onClickNextPage = () => {
    // 기존에 + 10
    setStartPage((prev) => prev + 10);
  };

  console.log(data);

  return (
    <div>
      {data?.fetchBoards.map((el) => (
        <MyRow key={el._id}>
          <MyColumn>{el.writer}</MyColumn>
          <MyColumn>{el.title}</MyColumn>
        </MyRow>
      ))}

      <span onClick={onClickPrevPage}>이전페이지</span>
      {new Array(10).fill(1).map((_, index) => (
        <span
          key={index + startPage}
          onClick={onClickPage}
          id={String(index + startPage)}
        >
          {index + startPage}
        </span>
      ))}
      <span onClick={onClickNextPage}>다음페이지</span>
    </div>
  );
}

위 로직에는 문제가 있다. - page 가 나오고, 마지막 페이지도 끝없이 나온다. 즉 시작과 끝을 정해줘야한다.


3) - page 로 안넘어가게 하고 마지막페이지 지정해주기

import { useQuery, gql } from "@apollo/client";
import styled from "@emotion/styled";
import { useState } from "react";

const FETCH_BOARDS = gql`
  query fetchBoards($page: Int) {
    fetchBoards(page: $page) {
      _id
      writer
      title
      contents
    }
  }
`;

// 게시글 총 개수 알기
// 객체로 받아오는 것이 아니라서 중괄호 필요 없음 
const FETCH_BOARD_COUNT = gql`
  query fetchBoardsCount {
    fetchBoardsCount
  }
`;

const MyRow = styled.div`
  display: flex;
  flex-direction: row;
`;
const MyColumn = styled.div`
  /* width: 25%; */
`;

export default function MapBoardPage() {
  const [startPage, setStartPage] = useState(1);
  const { data, refetch } = useQuery(FETCH_BOARDS);
  const { data: dataBoardsCount } = useQuery(FETCH_BOARD_COUNT);

  // 마지막 페이지 구하기 
  const lastPage = Math.ceil(dataBoardsCount?.fetchBoardsCount / 10);

  const onClickPage = (event) => {
    refetch({ page: Number(event.target.id) });
  };

  const onClickPrevPage = () => {
    // 이전 페이지 버튼을 눌렀을 때 - 페이지로 못 넘어가게 하기
    if (startPage === 1) return;
    setStartPage((prev) => prev - 10);
    refetch({ page: startPage - 10 });
  };

  // 1) lastPage 마지막 페이지 계산하기 ex) 35개 게시글이 있다면 한 페이지에 10개 나오니까 총 4페이지 (올림) 총 개수 나누기 10 하고 올림
  // 총 개수는 백엔드에서 제공을 해줌 fetchBoardsCount 글 총 개수를 10으로 나누고 올림을 해주면 마지막 페이지 lastPage 가 된다.
  // 2) startPage <= lastPage : 시작페이지는 마지막페이지보다 작거나 같아야 한다.
  const onClickNextPage = () => {
    // 전체 조건에 대해서 부정 
    if (!(startPage + 10 <= lastPage)) return;
    setStartPage((prev) => prev + 10); // 기존 +10
    refetch({ page: startPage + 10 });
  };

  return (
    <div>
      {data?.fetchBoards.map((el) => (
        <MyRow key={el._id}>
          <MyColumn>{el.writer}</MyColumn>
          <MyColumn>{el.title}</MyColumn>
        </MyRow>
      ))}

      <span onClick={onClickPrevPage}>이전페이지</span>
      {new Array(10).fill(1).map((_, index) =>
        index + startPage <= lastPage ? (
          <span
            key={index + startPage}
            onClick={onClickPage}
            id={String(index + startPage)}
          >
            {` `}
            {index + startPage}
          </span>
        ) : (
          <span></span>
        )
      )}
      <span onClick={onClickNextPage}>다음페이지</span>
    </div>
  );
}

페이지네이션을 다른 페이지에서도 사용하려면 따로 컴포넌트로 빼서 import 하는 형식으로 사용하면 된다.

import { useQuery, gql } from "@apollo/client";
import Board from "../../src/components/units/board/14-05-board-pagination/Board";
import PageNation from "../../src/components/units/board/14-05-board-pagination/Pagination";

const FETCH_BOARDS = gql`
  query fetchBoards($page: Int) {
    fetchBoards(page: $page) {
      _id
      writer
      title
      contents
    }
  }
`;

const FETCH_BOARD_COUNT = gql`
  query fetchBoardsCount {
    fetchBoardsCount
  }
`;

export default function MapBoardPage() {
  const { data, refetch } = useQuery(FETCH_BOARDS);

  const { data: dataBoardsCount } = useQuery(FETCH_BOARD_COUNT);
  const lastPage = Math.ceil(dataBoardsCount?.fetchBoardsCount / 10);

  return (
    <div>
      <Board data={data} />
  		// 페이지네이션 import 
  		// 코드의 길이가 짧아져서 가독성이 높아짐
      <PageNation refetch={refetch} lastPage={lastPage} />
    </div>
  );
}


무한 스크롤 (Infinite Scroll)

무한 스크롤은 사용자가 페이지 하단에 도달했을 때, 콘텐츠가 계속 로드되는 사용자 경험(UX, User EXperience) 방식이다.

이를 구현하기 위해 가장 많이 사용되는 react infinite scroller 를 사용해서 무한 스크롤을 구현해보자

https://www.npmjs.com/package/react-infinite-scroller

yarn add react-infinite-scroller
yarn add -D @types/react-infinite-scroller

{item} 이 보여질 데이터
hasMore: 데이터 더 있는지 없는지 check
loadMore: 데이터가 더 있으면 스크롤 내렸을 때 실행킬 함수

useWindow = {false} 지정하고, 스크롤을 감싸는 태그에 style로 높이를 설정하고 overflow:auto 를 준다. 그럼 지정한 높이보다 데이터의 길이가 크면 해당 박스에 스크롤이 작동한다. 위 두개가 빠지면 전체 바디 스크롤이 작동한다.


import { useQuery, gql } from "@apollo/client";
import styled from "@emotion/styled";
import InfiniteScroll from "react-infinite-scroller";

const FETCH_BOARDS = gql`
  query fetchBoards($page: Int) {
    fetchBoards(page: $page) {
      _id
      writer
      title
      contents
    }
  }
`;

const MyRow = styled.div`
  display: flex;
  flex-direction: row;
`;
const MyColumn = styled.div`
  width: 25%;
`;

export default function MapBoardPage() {
  const { data, fetchMore } = useQuery(FETCH_BOARDS); // fetchMore 추가적으로 10개를 더 페치한다

  // 📌 무한스크롤 함수
  const onLoadMore = () => {
    if (!data) return; // 만약에 데이터가 없다면 스크롤 실행 안됨

    // Q. refetch 와 fetchmore 차이는 ?
    // refetch : 기존에 했던 것을 다시 패치하는 것
    // fetchmore: 기존에 n개가 있었는데 추가로 더 fetch를 한다.
    fetchMore({
      variables: {
        // 현재까지 받은 페이지 fetchBoards 의 수 구하기
        // 기존에 받았던 데이터 갯수의 길이 / 10  = 현재까지 받은 페이지 수
        // + 1 = 다음페이지
        page: Math.ceil(data.fetchBoards.length / 10) + 1,
      },
      // updateQuery: 기존에 저장되어있던 쿼리를 업데이트
      // prev: 이전 데이터 1 ~ 10 (기존 페이지)
      // fetchMoreResult: 11 ~ 20 번 (추가로 요청한 페이지)
      updateQuery: (prev, { fetchMoreResult }) => {
        if (!fetchMoreResult.fetchBoards)
          return { fetchBoards: [...prev.fetchBoards] }; // 다음 표출될 데이터 없을 경우 기존 페치보드만 넣어주기

        return {
          fetchBoards: [...prev.fetchBoards, ...fetchMoreResult.fetchBoards], // 기존의 10개를 뿌리고, 추가로 받은 10개를 뿌리기 (이전 데이터와 다음 데이터 함께 표출 > 스프레드 연산자)
        };
      },
    });
  };

  return (
    <InfiniteScroll pageStart={0} loadMore={onLoadMore} hasMore={true}>
      {data?.fetchBoards.map((el: any) => (
        <MyRow key={el._id}>
          <MyColumn>{el._id}</MyColumn>
          <MyColumn>{el.writer}</MyColumn>
          <MyColumn>{el.title}</MyColumn>
        </MyRow>
      ))}
    </InfiniteScroll>
  );
}

profile
slow but sure

0개의 댓글