프론트엔드 - 11

송현섭 ·2023년 4월 1일

프론트엔드

목록 보기
12/24
post-thumbnail

페이지네이션 (Pagination)


페이지 번호를 클릭해서 이동하는 방식의 페이지 처리방법 (게시판 형태의 페이지에서 일반적으로 사용되는 방식)







+a) 페이지네이션 기능 만들기



1. 지정 번호 클릭 시 해당 번호의 페이지로 이동

// 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>
)
  • 아래쪽에 map 함수를 이용해서 각각의 번호를 생성하고 해당 번호마다 onClick={onClickPage} 라는 속성을 줌

  • onClick 속성이 부여된 해당 요소(태그) 를 선택할 때마다 지정한 id값( el )이 onClickPage 함수에 넘어가서 그 값을 page 값으로 하여 refetch 되고 , 결과적으로 해당 값(번호)의 페이지를 다시 fetch 하여 리렌더링하게 됨


    [BUT!] => 1~10 페이지까지만 로드되며, 11페이지 이상은 로드가 안 됨!







2. 이전 / 다음 버튼을 누르면 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>
  );
}
  • startPage 변수에 기본값을 1로 할당

  • 이전페이지, 다음페이지 버튼을 만들고 각각에 onClick 속성을 주어서 클릭 시 startPage 의 값이 + 10 or -10 이 되도록 지정

  • 아래 쪽 return 문 안의 map 함수에서 실질적으로 렌더링 되는 부분을 {index + startPage} 이런 식으로 지정
    *결과적으로 index값은 이전과 똑같이 0~9까지 반복되지만 이전페이지,다음페이지 버튼을 누를때마다 바뀌는 startPage의 값에 더해지면서 map으로 뿌려지는 부분이 변경됨


    [BUT!] => 이 경우 버튼을 클릭하면 이전페이지가 1 미만이어도 -1, -2, -3 이런 식으로 계속 렌더링되거나 마지막페이지를 지나도 계속 페이지 수가 늘어나게 됨

    (페이지 수의 한계에 대한 적절한 제어가 필요!)







3. 클릭 시 생성되는 페이지 수의 한계점 지정

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

 <button disabled={startPage === 1} onClick={ClickPrev}>{`<`}</button>

  • 이전페이지 버튼의 경우, 지정해 둔 변수 startPage 의 값이 1일 경우 (첫 10개의 페이지 1~10을 렌더링하고 있는 경우) return 하여 함수의 실행자체를 종료
    *페이지가 -1, -2,.. 이런 식으로 넘어가는 것을 방지

  • 추가적으로 버튼에 disabled 속성을 주어서 startPage의 값이 1일 경우(첫 페이지일 경우) 버튼 자체를 비활성화 시킬 수 있음





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

const {data: dataBoardsCount} = useQuery(FETCH_BOARDS_COUNT)



// lastPage 구하기
const lastPage = Math.ceil(dataBoardsCount?.fetchBoardsCount / 10);
  • 마지막페이지의 경우 우선 API 로 현재 페이지의 총 개수 (fetchBoardsCount) 를 가져 옴

  • 변수 lastPage 에 현재 총 페이지 수를 10으로 나눈 것을 올림한 값을 마지막 페이지 값으로 할당해줌
    -ex.
    1. 총 페이지가 1825개라면, 10개씩 나눴을 때 182개로 나뉘고 5가 남음(182.5)
    2. 나머지 5페이지도 화면에 표시는 되어야 하기 때문에 이 값을 올림 (183) 하여 그 다음 10페이지도 표시되게 하는 것




const onClickNextPage = () => {
	// startPage + 10가 lastPage보다 클 경우 하단 스크립트를 실행하지 않고 종료한다.
  if (startPage + 10 > lastPage) return;
  setStartPage((prev) => prev + 10);
};
  • 이후 다음페이지 버튼으로 실행되는 onClickNextPage 함수에 현재 startPage + 10 한 값이 마지막 페이지보다 클 경우 return 으로 함수를 종료
    -현재 페이지수가 1825개면, lastpage는 183이 됨
    -여기서 setStartpage가 한 번더 동작하면 페이지는 191~200까지를 리렌더링 할건데, 마지막 페이지가 183인 상황에서 그럴 필요가 없음으로 위와 같이 함수를 종료시켜 버리는 것





{new Array(10).fill(1).map(
  (_, index) =>
    index + startPage <= lastPage && (
      <span
        onClick={onClickPage}
        id={String(index + startPage)}
        key={index + startPage}
      >
        {` ${index + startPage} `}
      </span>
    )
)}
  • 이후 버튼 map 함수에 조건부렌더링으로 lastPage가 현재 startPage에 index값을 더한 거 보다 작을 경우에만 렌더링 되도록 설정







4. 이전페이지, 다음페이지 버튼 클릭 시 refetch 하기


// [1번째 방법]
const onClickPrevPage = () => {
	// startPage가 1이면 하단 스크립트를 실행하지 않고 종료한다.
  if (startPage === 1) return;
  setStartPage((prev) => prev - 10);
  refetch({ page: startPage - 10 });
};



// [2번째 방법]

const { data, refetch } = useQuery(FETCH_BOARDS, {
  variables: { page: startPage },
});
  • 렌더링 화면은 구성되었으나 기능이 아직 구현되지 않았기에 이전 / 다음 페이지를 클릭하면 refetch로 해당 화면을 다시 렌더링해줘야 함
    *위와 같이 startPage - 10의 값을 그대로 refetch의 page 값으로 할당해 주면 함수가 실행될 때마다 10페이지씩 감소된 페이지를 리렌더링

  • 또는 useQuery 함수 내 두번째 인자로 variables 변수에 page 값을 startPage로 지정해 줄 수도 있음
    *여기에서 variables는 변수를 필요로 하는 모든 실행함수(ex. refetchQueries) 의 변수 값으로 전역 지정 됨








State 끌어올리기(state lifting)


  • 기본적으로 React의 데이터 흐름은 상위 컴포넌트에서 하위 컴포넌트로 전달하는 단방향 데이터 흐름을 취하고 있음 (부모 -> 자식 (O) / 자식 -> 부모 (X))




단방향 데이터 흐름의 단점

  • 단방향에서 데이터는 위에서 아래로 흐르기 때문에, 위 같은 경우와 같이 자식1의 state 값을 자식2 에게 보내주는 것이 불가능함

  • 또한 자식의 state를 부모에게 보내주는 것도 불가능함






해결책

  • 자식 컴포넌트의 state와 setState를 부모 컴포넌트로 끌어올려 선언 해주면 됨 (state 끌어올리기)

  • 그리고 props 로 내려줄 경우, 자식 컴포넌트1, 2에서 모두 state를 사용할 수 있게 됨





state 끌어올리기 예제

// 자식 1 - 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>
  );
}
  • 우선적으로 부모 컴포넌트에 useState 로 count, setCount 만든 후 이를 자식 1 컴포넌트에 props 로 상속

  • 자식 1 컴포넌트에서 상속받은 props.setCount로 버튼을 클릭할 때마다 count 값이 변경되도록 설정





// 자식 컴포넌트 - Child2.tsx
export default function Child2(props) {
  return (
    <div>
      <div>자식 2 카운트: {props.count}</div>
      <button>카운트 올리기</button>
    </div>
  );
}
  • 자식 2에도 마찬가지로 부모 컴포넌트에서 props로 상속 받은 count 값을 설정




    [결과]

  • 자식 1의 카운트 버튼을 클릭하면 자식2의 state 값도 변경되어 변경되는 카운트 값이 적용 됨

profile
막 발걸음을 뗀 신입

0개의 댓글