2023. 3. 30

Junghan Lee·2023년 3월 30일
0

TIL Diary

목록 보기
28/52

Index

페이지네이션
state끌어올리기


intro

페이지네이션

페이지처리를 하는 방법에는 크게 일반적인 방식, 무한스크롤 방식 2개가 있다.
페이지네이션이란 페이지 번호를 클릭해 이동하는 방식의 페이지 처리 방법이다.
또한 이 방법은 게시판 형태의 페이지에서 가장 일반적으로 사용된다.

페이지네이션은 쉬워보이지만 이를 처리하기 위해서는 다양한 고려사항이 있다.

먼저, page인자를 사용해 게시글 목록을 불러온다.

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

// fetchBoards
const { data } = useQuery(FETCH_BOARDS, { variables: { page: 1 } });

return (
	<div>
    <h1>페이지네이션 연습 !!!</h1>
    {data?.fetchBoards?.map((el) => (
      <div key={el._id}>
        {el.title} {el.writer}
      </div>
    ))}
		<span> 1 </span>
		<span> 2 </span>
		<span> 3 </span>
  </div>
)


아래는 결과물이다.

두번째로, 페이지 클릭 시 게시글 목록 데이터를 다시 불러온다(refetch)
페이지네이션의 페이지 숫자를 클릭할 떄마다 목록에 뿌려진 데이터가 해당 페이지에 해당하는 데이터로 변경되도록 한다.
graphQL의 useQuery에서 제공하는 refetch를 사용하면 페이지 클릭 시 해당 페이지의 데이터를 다시 불러올 수 있다. refetch를 사용하기 위해서는 useQuery에서 data와 함께 refetch 함수를 불러와야 한다.

필요한 부분에 불러온 refetch함수를 넣어주고 refetch의 인자에 변경된 variables(이 경우, page)를 입력해 보면..

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

// fetchBoards
// data와 함께 refetch 가져오기
const { data, refetch } = useQuery(FETCH_BOARDS, { variables: { page: 1 } });

const onClickPage = (event) => {
	// 위에서 가져온  refetch 사용하기
  refetch({ page: Number(event.target.id) });
};

return (
	<div>
    <h1>페이지네이션 연습 !!!</h1>
    {data?.fetchBoards?.map((el) => (
      <div key={el._id}>
        {el.title} {el.writer}
      </div>
    ))}
		<span onClick={onClickPage} id="1"> 1 </span>
		<span onClick={onClickPage} id="2"> 2 </span>
		<span onClick={onClickPage} id="3"> 3 </span>
  </div>
)

하단의 1을 클릭하면 1페이지의 게시글 목록이, 각각의 숫자에 해당하는 게시글 목록이 보이게 된다.

세번째, map을 이요해 페이지네이션 뿌리기
모든 페이지네이션의 숫자를 직접 입력하는 것은 비효율적이기 때문에 배열과 map을 사용하면 효율적일 수 있다.

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

// fetchBoards
// data와 함께 refetch 가져오기
const { data, refetch } = useQuery(FETCH_BOARDS, { variables: { page: 1 } });

const onClickPage = (event) => {
	// 위에서 가져온  refetch 사용하기
  refetch({ page: Number(event.target.id) });
};

return (
	<div>
    <h1>페이지네이션 연습 !!!</h1>
    {data?.fetchBoards?.map((el) => (
      <div key={el._id}>
        {el.title} {el.writer}
      </div>
    ))}
		{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((el) => (
        <span onClick={onClickPage} id={String(el)} key={el}>
          {` ${el} `}
        </span>
      ))}
  </div>
)

하지만 이렇게 직접 숫자값을 입력하면 1~10페이지까지밖에 만들 수 없다. 그렇다면?

네번째, 페이지네이션 next/prev구현

{new Array(10).fill(1).map((_, index) => (
  <span
    onClick={onClickPage}
    id={String(index + startPage)}
    key={index + startPage}
  >
    {` ${index + startPage} `}
  </span>
))}

이 경우 startPage의 값을 변경해주면 현재 페이지 이후의 10페이지, 이전의 10페이지를 불러오는 기능 또한 구현이 가능하다. 코드는 다음과 같다.

export default function PaginationNextPage() {
	// 시작 페이지
  const [startPage, setStartPage] = useState(1);
  const { data, refetch } = useQuery(FETCH_BOARDS, { variables: { page: 1 } });

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

	// 이전 페이지 클릭 시 실행할 함수
  const onClickPrevPage = () => {
    setStartPage((prev) => prev - 10);
  };
	// 다음 페이지 클릭 시 실행할 함수
  const onClickNextPage = () => {
    setStartPage((prev) => prev + 10);
  };

  return (
    <div>
      <h1>페이지네이션 연습 !!!</h1>
      {data?.fetchBoards?.map((el) => (
        <div key={el._id}>
          {el.title} {el.writer}
        </div>
      ))}

      <span onClick={onClickPrevPage}>이전페이지 |</span>

      {new Array(10).fill(1).map((_, index) => (
        <span
          onClick={onClickPage}
          id={String(index + startPage)}
          key={index + startPage}
        >
          {` ${index + startPage} `}
        </span>
      ))}

      <span onClick={onClickNextPage}> 다음페이지</span>

    </div>
  );
}

여섯 번째, lastPage 설정하기

페이지네이션이 마지막 페이지를 지나가도 계속해서 다음 페이지를 불러온다. 그렇다면?
데이타베이스에 등록된 게시글의 총 개수를 불러와 마지막 페이지의 값을 구현한다.

// fetchBoardsCount API 요청하기
const FETCH_BOARDS_COUNT = gql`
 query fetchBoardsCount {
   fetchBoardsCount
 }
`;

// lastPage 구하기
const lastPage = Math.ceil(dataBoardsCount?.fetchBoardsCount / 10);

이후 이전페이지, 다음 페이지를 클릭했을 때 실행되는 함수에 조건을 설정해
1페이지 미만, 그리고 lastPage가 화면에 출ㄹ격된 이후로는 이전 페이지, 다음 페이지 버튼이 동작하지 않도록 만든다.

const onClickPrevPage = () => {
	// startPage가 1이면 하단 스크립트를 실행하지 않고 종료한다.
  if (startPage === 1) return; 
  setStartPage((prev) => prev - 10);
};
const onClickNextPage = () => {
	// startPage + 10가 lastPage보다 클 경우 하단 스크립트를 실행하지 않고 종료한다.
  if (startPage + 10 > lastPage) return;
  setStartPage((prev) => prev + 10);
};

그리고 페이지네이션 map에 조건부 렌더링을 걸어 lastPage보다 큰 숫자는 출력되지 않도록 만든다.

{new Array(10).fill(1).map(
  (_, index) =>
    index + startPage <= lastPage && (
      <span
        onClick={onClickPage}
        id={String(index + startPage)}
        key={index + startPage}
      >
        {` ${index + startPage} `}
      </span>
    )
)}

일곱번째, 이전페이지, 다음페이지 이동 시 리페치

마지막으로 이전/다음 페이지를 눌러 이동할 경우
11~20페이지라면 11페이지로 21~30페이지라면 21페이지로 게시글 목록이 함께 이동되어야 한다.

이를 위해 리페치를 이용하는 방법이 있다.

const onClickPrevPage = () => {
	// startPage가 1이면 하단 스크립트를 실행하지 않고 종료한다.
  if (startPage === 1) return;
  setStartPage((prev) => prev - 10);
  refetch({ page: startPage - 10 });
};
const onClickNextPage = () => {
	// startPage + 10가 lastPage보다 클 경우 하단 스크립트를 실행하지 않고 종료한다.
  if (startPage + 10 > lastPage) return;
  setStartPage((prev) => prev + 10);
  refetch({ page: startPage + 10 });
};

두번째론 게시글 목록을 불러오기 위해 useQuery에 넣는 변수에 startPage라는 state를 넣는 방법이 있다.
이러면 이전/다음 펲이지를 눌러 startPage가 변경될 때마다 바뀐 startPage의 데이터가 새로 뿌려지게 된다.

// 기존 useQuery
const { data, refetch } = useQuery(FETCH_BOARDS, {
  variables: { page: 1 },
});

// 변경 후 useQuery
const { data, refetch } = useQuery(FETCH_BOARDS, {
  variables: { page: startPage },
});

심화) 게시판을 이용하는 사용자가 현재 페이지가 몇 페이지인지 알수 있도록 현재 페이지를 다른 페이지와 다르게 표시하려면?
emotion에 props를 넘겨주는 방법이 있고 다양한 방법이 있다.

state끌어올리기

react데이터의 흐름은 상위에서 하위로 전달하는 하향식, 단방향 데이터 흐름이다.
장점은 기능 변경 사항에 대한 코드 수정이 적어지고 복잡하지 않아 코드의 흐름을 알기 쉽다. 그러나 단점 또한 존재한다.

단방향 데이터 흐름의 경우, 위의 구조에서 자식1의 state를 자식2에서 보여주는 것이 불가능하며 자식2의 state를 부모에서 보여주는 것 또한 불가능하다.

이를 해결하기 위해서는 state와 setState를 부모로 끌어올려 선언해주면 된다. props로 내려줄 경우, 자식 1, 2 에서 모두 사용할 수 있다.

profile
Strive for greatness

0개의 댓글