[코드캠프]15일차_TIL_페이지네이션

윤성해·2023년 3월 30일
0

프론트엔드_TIL

목록 보기
14/27
post-thumbnail

🏷️ 수업 목표

  1. 페이지네이션
  2. State 끌어올리기

TIL

페이지네이션이란?

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

페이지 네이션은 고려해야할 부분이 많습니다.

1. page 인자를 사용해서 게시글 목록 불러오기

가장 먼저 fetchBooards API를 활용해서 게시글 목록을 불러옵니다. 이 때, 플레이그라운드 독스를 참고하여 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>
)


그러면 게시글이 쭉 뜨게됩니다.

2. 페이지 클릭 시 게시글 목록 데이터 다시 불러오기 (refetch)

페이지네이션의 페이지 숫자를 클릭할 때마다 목록에 뿌려진 데이터가 해당 페이지에 해당하는 데이터로 변경되도록 해봅시다. graphQL의 useQuery에서 제공하는 refetch 함수를 사용하면, 페이지 클릭 시 해당 페이지에 해당하는 데이터를 다시 불러올 수 있습니다. refetch를 사용하기 위해서는 useQuery에서 data와 함께 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페이지의 게시글 목록이 보여지고, 2,3페이지의 게시글 목록도 볼 수 있습니다.

3. 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페이지의 페이지네이션밖에 만들지 못합니다.

4. 페이지네이션의 next, prev 구현

map을 사용할 때 index도 인자로 받아올 수 있는데, 이것을 활용하면 다음과 같은 방식으로 페이지네이션을 만들 수 있습니다.

{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>
  );
}

5. lastPage 설정

그런데, 마지막 페이지를 훌쩍 지나갔는데도 계속해서 다음 페이지를 불러옵니다.
마지막 페이지를 설정해주려면, 가장 먼저 DB에 등록된 게시글의 총 개수를 불러와서 마지막 페이지의 값을 구합니다.

// 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>
    )
)}

6. 이전 페이지/다음 페이지 이동시 refetch

이전페이지, 다음페이지 버튼을 눌러서 이동할 경우,
11-20 페이지라면 11페이지가,
21-30 페이지라면 21페이지가 처음 보이도록 게시글 목록이 함께 이동되어야 합니다.

  1. 첫번째 방법refetch를 이용하는 방법입니다.
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 });
};
  1. 두 번째 방법은 게시글 목록을 불러오기 위하여 useQuery에 넣는 variables에 startPage라는 state를 넣어주는 방법입니다.
    이렇게 하면 이전 페이지, 다음 페이지를 눌러서 startPage가 변경될 때마다
    바뀐 startPage의 데이터가 새로 뿌려지게 됩니다.
// 기존 useQuery
const { data, refetch } = useQuery(FETCH_BOARDS, {
  variables: { page: 1 },
});

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

state 끌어올리기

React의 데이터 흐름은 상위 컴포넌트에서 하위 컴포넌트로 전달하는 하향식, 단방향 데이터 흐름을 따르고 있습니다.

그렇다면 단방향 데이터 흐름의 장점은 무엇일까요? 우선, 기능 변경 사항에 대한 코드 수정이 적어집니다. 또한 복잡하지 않아 코드의 흐름을 알기 쉽다는 점입니다. 하지만 단방향 데이터 흐름의 단점도 존재합니다.

단방향 데이터 흐름의 경우,
위와 같은 구조에서 자식 컴포넌트1의 state를 자식 컴포넌트2에서 보여주는 것불가능합니다.
또한, 자식 컴포넌트2의 state를 부모 컴포넌트에서 보여주는 것불가능합니다.


이럴 경우, 자식 컴포넌트의 state와 setState를 부모 컴포넌트로 끌어올려 선언해주면 됩니다.
그리고 props로 내려줄 경우, 자식 컴포넌트1, 2에서 모두 state를 사용할 수 있게 됩니다.

state 끌어올리기 실습
하나의 부모 컴포넌트에 두 개의 자식 컴포넌트를 import 해봅시다. 그리고 부모 컴포넌트에서 생성한 state와 setState를 props를 이용해 자식 컴포넌트로 넘겨줍니다.

// 부모 컴포넌트 - index.tsx
import Child1 from "../../src/components/units/14-lifting-state-up/Child1";
import Child2 from "../../src/components/units/14-lifting-state-up/Child2";
import { useState } from "react";

export default function LiftingStateUpPage() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <Child1 count={count} setCount={setCount} />
      <div>=========================================</div>
      <Child2 count={count} />
    </div>
  );
}
// 자식 컴포넌트 - Child1.tsx
export default function Child1(props) {

  const onClickCountUp = () => {
    props.setCount((prev) => prev + 1);
  };

  return (
    <div>
      <div>자식 1 카운트: {props.count}</div>
      <button onClick={onClickCountUp}>카운트 올리기</button>
    </div>
  );
}
// 자식 컴포넌트 - Child2.tsx
export default function Child2(props) {
  return (
    <div>
      <div>자식 2 카운트: {props.count}</div>
      <button>카운트 올리기</button>
    </div>
  );
}

그리고 자식컴포넌트 1에 있는 카운트 올리기 버튼을 클릭해보며느 자식컴포넌트 2로 보낸 count 라는 state도 함께 증가하는 것을 볼 수 있습니다!!


추가 수업내용

나만 알아볼 수 있는 내용..

💡 리팩토링 하기

  • 캐러셀 넣을 때 요거 넣어야 잘 나온다. 참고!
  • navigation 컨테이너, 프레젠터 리팩토링 하기 전 코드

    여기에 결제페이지로 가려면 프레젠트에 온클릭하나 추가, 컨테이너에 함수 하나 추가, 타입(지금은 any) 추가 프롭스로 넘겨줄거 하나 추가 .... 엄청 많다!
    ➡️ 컨테이너에 보면 함수 안에 router.push 가 반복된다.

💡 리팩토링 해보면!!(리팩토링 방법)

어떤게 중복되고, 반복되는가?? 를 생각하기

프레젠터에 아이디 추가하고, 이벤트 타겟 아이디 하면 그게 들어오니까 그걸 받아오면, 하드코딩 하지 않아도 된다.

그런데, 이렇게 보니 onClick 함수 이름이 똑같다.
onClickMenu 로 이름 바꾸고 나머지 지워주자!

그런데! 여기서 프레젠터쪽 보면 id값 빼고 모두 같다.
우리가 배열에 철수, 영희, 훈이 가 있을 때 이것을 우리는 map 을 통해서 그릴 수 있었다.

그럼 이걸 활용해서, map쓰고 다른것들은el.id, el.name 을 써서 뽑아오면 되겠다.

이렇게 맵을 만들어보고

자바스크립트 안으로 {} 써서 넣어준다.

상속값들은 위에 변수명으로 묶어서 빼주고, 변수명.map 해서 자바스크립트 안에서 돌려보자. (key값 추가) LAYOUT_MENU 에 넣고싶은 메뉴 만들어서 넣오면, 메뉴에서 방금 만들어준 메뉴가 바로 나오게 된다!!


페이지네이션



data가 1에서 2 로 바뀌고, 대이터는 ()니까 리랜더가 되고 2페이지가 보인다. 리패치는 베리어블즈 생략 가능

함수안에서 기다리고 다음로직 시키고 싶을 때 에이싱크, 어웨이트

근데 우리는 기다릴 필요 없음

실습

위 123을 스팬으로 아이디값 준다

위와같이 하고 3개는 주석처리해주기

얘를 리팩토링 해보면, 1, 2, 3만 빼고 다 다르다

map을 이용해서 반복문 만들고, {}는 자바스크립트니까 써주고 안에 [] 넣어서 넣어주기.id값은 (1페이지의1) 스트링 타입이라서 스트링으로 묶어준다.

[1,2,3,4,5,6,7,8,9,10] 이렇게 쓰면 10페이지 까지 나오는데 이렇게 쓰기 귀찮다?

el,index

ㅇ렇게 철수 4개 쓰면 인덱스로 밑에 숫자가 4페이지까지 나온다. 그럼 철스 10개 언제적냐.... 얘를 묶어서 배열에 넣어보자

이거써서

다음페이지 넘어가기 (11~20페이지)


  • 다음페이지 누르면
  1. 다음페이지 숫자 나와야함
  2. 다음페이지 첫 페이지로 화면 리패치(11페이지가 보이게)

    하드코딩된 1을 스테이트에 저장하고 다음버튼 눌리면 + 10 해주기

  3. 여기 위에 원래 1 있던거 스타트페이지로 바끔

  4. 온클릭 함수에 스타트 페이지 + 10, -10 해준다!!!!!!
    그리고 리패치 해줘서 다음페이지 가면 화면 변경될 수 있도록 해주었음.

더이상 다음페이지가 없을 때?

문제상황 : 1-10 페이지 에서 이전페이지 누르면 -9...음수가 나온다!!?? 283페이지가 끝이고 다음페이지는 안나와야하는데 284, 285 얘네도 클릭이 가능하다.

어쩌죠?
1. 마지막 페이지 계산하기 (로직)
startPage(281) + 10 <= 마지막페이지(283) -> 291이 283을 넘을 수 없다!
2. 다음페이지 누를 때, if(startPage + 10 <= 마지막페이지) 인지 확인하기 조건문!!!

근데 마지막페이지 어떻게 구하지??

  • 공식
    총갯 나누기 10 하면 8.3 여기서 0.3 은 올림하기. 보여주긴 해야하니까 그러면 (9)가 나온다. Math.ceil(totalCount / 10)

datadlfmad이름ㄷ바꾸기


뒤에 283까지 있는거만 나오게 해주는거


state 끌어올릭

형제 둘이 스테이트를 공유하고 싶다. 어떻게 할까?
부모로 끌어올리자. 그러면 부모의 스테이트를 형제 둘한테 프롭스로 넘겨주자


스테이트가 부모가 같아서 형제지만
자식이지만 부모의 스테이트가 바뀌고 그게 프롭스로 내려와서 그게 자식한테 다시반영이 더ㅣㄴ다. 마치 서로간의 스테이트를 공유하는 형태이다.

이렇게 가져와도 괜찮고

하나는 이렇게 셋카운트 가져와도 된다

이거 언제쓰냐
상품 목록과 페이지네이션 / 게시글목록과 페이지네이션 이 있을 때 매번 페이지네이션 똑같은것을 계속 만들어야하나? 페이지네이션 하나만들고 상품목록과 게시글목록 밑에 부착하면 된다.

리패치 앞에있는 보이드가 뭘까?
리패치 하면 요청이 나간다. 리패치 기다렸다가 밑에 실행하고싶다면 어웨이트
근데 그거 아니고 상관 없으면(안기다려도 괜찮으면) void 쓰기(eslint가 쓰라고 하는고)

현재페이지가 281 이다 인덱스 0

라스트 페이지보다 작을때 네모칸. 그 이후부터는 빨간즐 쳐진 빈 스펜을 보여주게 된다. 근데 이렇게 ? 안써도 조건부렌더링 && 해서 284 안으로만 나오게 쓸 수 있다.


🤷🏻‍♀️ 궁금한 것


❗️ 배운 것


✨ 느낀점

profile
Slow and steady wins the race.

0개의 댓글